mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-09-14 21:20:10 +00:00
Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1e0427468b | ||
![]() |
17ec4b9a57 | ||
![]() |
1cd0243082 | ||
![]() |
9f5c1a985f | ||
![]() |
0db0c4eccd | ||
![]() |
65be967db2 | ||
![]() |
0c47537866 | ||
![]() |
25f8f8f404 | ||
![]() |
07e2678fd4 | ||
![]() |
4f2ec8425e | ||
![]() |
1df7eb4eea | ||
![]() |
56285d7624 | ||
![]() |
9d726e6424 | ||
![]() |
ba8789a099 | ||
![]() |
5f75d070ca | ||
![]() |
e8666811f2 | ||
![]() |
7a2abb4d9c | ||
![]() |
a2db30bf33 | ||
![]() |
56cc47da34 | ||
![]() |
8b6d8ef2e2 | ||
![]() |
d1a2bdf599 | ||
![]() |
a2cac61242 | ||
![]() |
f6d19ed304 | ||
![]() |
2aab843125 | ||
![]() |
a9a0e28ffb | ||
![]() |
4e2d4ca040 | ||
![]() |
5136c24602 | ||
![]() |
22ba9f64aa | ||
![]() |
87152a5dfa | ||
![]() |
24755fb621 | ||
![]() |
7ccd57613b | ||
![]() |
a0c87d46e7 | ||
![]() |
8343343ea1 | ||
![]() |
fe51e1dcf4 | ||
![]() |
3b27f56e89 | ||
![]() |
da16d7fed6 | ||
![]() |
79ca1f221b | ||
![]() |
33ac7e9f7a | ||
![]() |
e8ca36a430 | ||
![]() |
7d4bce440b | ||
![]() |
52ad5226df | ||
![]() |
4d5a69ad2d | ||
![]() |
d879bc533f | ||
![]() |
bb8dd6a5c4 | ||
![]() |
3d1c4eb9e9 | ||
![]() |
89de3b78d0 | ||
![]() |
9c88f70f21 | ||
![]() |
0a259d1b5b | ||
![]() |
d4124f5bec | ||
![]() |
ca07e270a8 | ||
![]() |
233eff599e | ||
![]() |
992c40b5d4 | ||
![]() |
9061b6823d | ||
![]() |
36d1b55ca5 | ||
![]() |
fcad97f916 | ||
![]() |
7d80bb0b74 | ||
![]() |
6ef5cff8cc | ||
![]() |
f9353f0196 | ||
![]() |
8164d8ff7a | ||
![]() |
58a3ec951f | ||
![]() |
16c4f03e6e | ||
![]() |
0918f5a782 | ||
![]() |
59c3811b2e | ||
![]() |
f1d60599c1 | ||
![]() |
c6661b242b | ||
![]() |
7c718de890 | ||
![]() |
d940cc4178 | ||
![]() |
519836a9a5 | ||
![]() |
13c9ad8b88 | ||
![]() |
e81eaf4fe6 | ||
![]() |
2b5d55860a | ||
![]() |
b5d3c5bbbd | ||
![]() |
6f36d65b22 | ||
![]() |
58965c4a07 | ||
![]() |
37d5b1b7e0 | ||
![]() |
cebe0b8a21 | ||
![]() |
910cf7a67f | ||
![]() |
c25094d299 | ||
![]() |
44f9d36de5 | ||
![]() |
aec51e0c6f | ||
![]() |
add7ec1e1e | ||
![]() |
73ab650366 | ||
![]() |
23195c2c98 | ||
![]() |
4a68712379 | ||
![]() |
22ae328d75 | ||
![]() |
f6eef13447 | ||
![]() |
1a6743fd59 | ||
![]() |
6e9e0ab008 | ||
![]() |
d1abe44eea | ||
![]() |
8c48e09705 | ||
![]() |
5743fd9cfc | ||
![]() |
1189a051c6 | ||
![]() |
3e1dd50e12 | ||
![]() |
a0b04a2d9e | ||
![]() |
079ab96cc8 | ||
![]() |
f09fe925ff | ||
![]() |
2735a14497 | ||
![]() |
3072800286 | ||
![]() |
a7d31ef55f | ||
![]() |
27f56742f1 | ||
![]() |
91d19e2ba1 | ||
![]() |
330b2c334b | ||
![]() |
09accc8cc5 | ||
![]() |
a515fdc82f | ||
![]() |
bd350d7105 | ||
![]() |
740ac31cbf | ||
![]() |
a7c8e87966 | ||
![]() |
457bd26afa | ||
![]() |
81383752f7 | ||
![]() |
f6e3c37c52 | ||
![]() |
e56e7b3b3e | ||
![]() |
faf8207808 | ||
![]() |
36026fb557 | ||
![]() |
976280e5a2 | ||
![]() |
ff03c136e1 | ||
![]() |
b3a5219d96 | ||
![]() |
d7cc404968 | ||
![]() |
38e5a455ea | ||
![]() |
053be79110 | ||
![]() |
46322ae532 | ||
![]() |
63c857c4c3 | ||
![]() |
af1a3dd697 | ||
![]() |
d6875797ff | ||
![]() |
78442283ea | ||
![]() |
f8700f9928 | ||
![]() |
70982b6ccd | ||
![]() |
87d0c1886f | ||
![]() |
81d7f8fa83 | ||
![]() |
23c442b062 | ||
![]() |
8a65ebbe3c | ||
![]() |
c9e6c03652 | ||
![]() |
5fa5ddc78e | ||
![]() |
d97cc095e3 | ||
![]() |
00ef89069d | ||
![]() |
09269cb94e | ||
![]() |
825d5a3b07 | ||
![]() |
cd09273080 | ||
![]() |
23fe39f432 | ||
![]() |
485560ba64 | ||
![]() |
80c433689b | ||
![]() |
b3e4e36d10 | ||
![]() |
bccb2441b9 | ||
![]() |
ce0381c170 | ||
![]() |
dfbc5ea5a6 | ||
![]() |
9b6aaa08d4 | ||
![]() |
aaa4c52374 | ||
![]() |
edd415c958 | ||
![]() |
1bfce40f61 | ||
![]() |
a0726617e5 |
29
Gruntfile.js
29
Gruntfile.js
@ -51,7 +51,8 @@ module.exports = function(grunt) {
|
||||
ignore: ['*.min.js'],
|
||||
presets: ["es2015"]
|
||||
}]
|
||||
]
|
||||
],
|
||||
plugin: [ 'browserify-derequire' ]
|
||||
}
|
||||
},
|
||||
openpgp_debug: {
|
||||
@ -69,7 +70,8 @@ module.exports = function(grunt) {
|
||||
ignore: ['*.min.js'],
|
||||
presets: ["es2015"]
|
||||
}]
|
||||
]
|
||||
],
|
||||
plugin: [ 'browserify-derequire' ]
|
||||
}
|
||||
},
|
||||
worker: {
|
||||
@ -77,11 +79,6 @@ module.exports = function(grunt) {
|
||||
'dist/openpgp.worker.js': [ './src/worker/worker.js' ]
|
||||
}
|
||||
},
|
||||
worker_min: {
|
||||
files: {
|
||||
'dist/openpgp.worker.min.js': [ './src/worker/worker.js' ]
|
||||
}
|
||||
},
|
||||
unittests: {
|
||||
files: {
|
||||
'test/lib/unittests-bundle.js': [ './test/unittests.js' ]
|
||||
@ -108,12 +105,20 @@ module.exports = function(grunt) {
|
||||
to: 'OpenPGP.js v<%= pkg.version %>'
|
||||
}]
|
||||
},
|
||||
openpgp_min: {
|
||||
src: ['dist/openpgp.min.js'],
|
||||
dest: ['dist/openpgp.min.js'],
|
||||
replacements: [{
|
||||
from: "openpgp.worker.js",
|
||||
to: "openpgp.worker.min.js"
|
||||
}]
|
||||
},
|
||||
worker_min: {
|
||||
src: ['dist/openpgp.worker.min.js'],
|
||||
dest: ['dist/openpgp.worker.min.js'],
|
||||
replacements: [{
|
||||
from: "importScripts('openpgp.js')",
|
||||
to: "importScripts('openpgp.min.js')"
|
||||
from: "openpgp.js",
|
||||
to: "openpgp.min.js"
|
||||
}]
|
||||
}
|
||||
},
|
||||
@ -121,7 +126,7 @@ module.exports = function(grunt) {
|
||||
openpgp: {
|
||||
files: {
|
||||
'dist/openpgp.min.js' : [ 'dist/openpgp.js' ],
|
||||
'dist/openpgp.worker.min.js' : [ 'dist/openpgp.worker.min.js' ]
|
||||
'dist/openpgp.worker.min.js' : [ 'dist/openpgp.worker.js' ]
|
||||
}
|
||||
},
|
||||
options: {
|
||||
@ -290,7 +295,9 @@ module.exports = function(grunt) {
|
||||
}
|
||||
|
||||
// Build tasks
|
||||
grunt.registerTask('default', ['clean', 'copy:zlib', 'browserify', 'replace', 'uglify']);
|
||||
grunt.registerTask('version', ['replace:openpgp', 'replace:openpgp_debug']);
|
||||
grunt.registerTask('replace_min', ['replace:openpgp_min', 'replace:worker_min']);
|
||||
grunt.registerTask('default', ['clean', 'copy:zlib', 'browserify', 'version', 'uglify', 'replace_min']);
|
||||
grunt.registerTask('documentation', ['jsdoc']);
|
||||
// Test/Dev tasks
|
||||
grunt.registerTask('test', ['jshint', 'jscs', 'mochaTest']);
|
||||
|
92
README.md
92
README.md
@ -21,7 +21,7 @@ OpenPGP.js [ via the `window.crypto.subtle` api, this will be used. Under node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used. This can be deactivated by setting `openpgp.config.use_native = false`.
|
||||
|
||||
* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ford-openpgp-format-00) for authenticated encryption [using native AES-GCM](https://github.com/openpgpjs/openpgpjs/pull/430). This makes symmetric encryption about 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. You can activate it by setting `openpgp.config.aead_protect = true`.
|
||||
* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ford-openpgp-format-00) for authenticated encryption [using native AES-GCM](https://github.com/openpgpjs/openpgpjs/pull/430). This makes symmetric encryption about 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. You can activate it by setting `openpgp.config.aead_protect = true`. **Note: activating this setting can break compatibility with other OpenPGP implementations, so be careful if that's one of your requirements.**
|
||||
|
||||
* For environments that don't provide native crypto, the library falls back to [asm.js](http://caniuse.com/#feat=asmjs) implementations of AES, SHA-1, and SHA-256. We use [Rusha](https://github.com/srijs/rusha) and [asmCrypto Lite](https://github.com/openpgpjs/asmcrypto-lite) (a minimal subset of asmCrypto.js built specifically for OpenPGP.js).
|
||||
|
||||
@ -87,12 +87,16 @@ openpgp.decrypt(options).then(function(plaintext) {
|
||||
var options, encrypted;
|
||||
|
||||
var pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK ... END PGP PUBLIC KEY BLOCK-----';
|
||||
var privkey = '-----BEGIN PGP PRIVATE KEY BLOCK ... END PGP PRIVATE KEY BLOCK-----';
|
||||
var privkey = '-----BEGIN PGP PRIVATE KEY BLOCK ... END PGP PRIVATE KEY BLOCK-----'; //encrypted private key
|
||||
var passphrase = 'secret passphrase'; //what the privKey is encrypted with
|
||||
|
||||
var privKeyObj = openpgp.key.readArmored(privkey).keys[0];
|
||||
privKeyObj.decrypt(passphrase);
|
||||
|
||||
options = {
|
||||
data: 'Hello, World!', // input as String (or Uint8Array)
|
||||
publicKeys: openpgp.key.readArmored(pubkey).keys, // for encryption
|
||||
privateKeys: openpgp.key.readArmored(privkey).keys // for signing (optional)
|
||||
privateKeys: privKeyObj // for signing (optional)
|
||||
};
|
||||
|
||||
openpgp.encrypt(options).then(function(ciphertext) {
|
||||
@ -104,7 +108,7 @@ openpgp.encrypt(options).then(function(ciphertext) {
|
||||
options = {
|
||||
message: openpgp.message.readArmored(encrypted), // parse armored message
|
||||
publicKeys: openpgp.key.readArmored(pubkey).keys, // for verification (optional)
|
||||
privateKey: openpgp.key.readArmored(privkey).keys[0] // for decryption
|
||||
privateKey: privKeyObj // for decryption
|
||||
};
|
||||
|
||||
openpgp.decrypt(options).then(function(plaintext) {
|
||||
@ -151,6 +155,86 @@ var pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK ... END PGP PUBLIC KEY BLOCK-----'
|
||||
hkp.upload(pubkey).then(function() { ... });
|
||||
```
|
||||
|
||||
#### Sign and verify cleartext messages
|
||||
|
||||
```js
|
||||
var options, cleartext, validity;
|
||||
|
||||
var pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK ... END PGP PUBLIC KEY BLOCK-----';
|
||||
var privkey = '-----BEGIN PGP PRIVATE KEY BLOCK ... END PGP PRIVATE KEY BLOCK-----'; //encrypted private key
|
||||
var passphrase = 'secret passphrase'; //what the privKey is encrypted with
|
||||
|
||||
var privKeyObj = openpgp.key.readArmored(privkey).keys[0];
|
||||
privKeyObj.decrypt(passphrase);
|
||||
```
|
||||
|
||||
```js
|
||||
options = {
|
||||
data: 'Hello, World!', // input as String (or Uint8Array)
|
||||
privateKeys: privKeyObj // for signing
|
||||
};
|
||||
|
||||
openpgp.sign(options).then(function(signed) {
|
||||
cleartext = signed.data; // '-----BEGIN PGP SIGNED MESSAGE ... END PGP SIGNATURE-----'
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
options = {
|
||||
message: openpgp.cleartext.readArmored(cleartext), // parse armored message
|
||||
publicKeys: openpgp.key.readArmored(pubkey).keys // for verification
|
||||
};
|
||||
|
||||
openpgp.verify(options).then(function(verified) {
|
||||
validity = verified.signatures[0].valid; // true
|
||||
if (validity) {
|
||||
console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Create and verify *detached* signatures
|
||||
|
||||
```js
|
||||
var options, cleartext, detachedSig, validity;
|
||||
|
||||
var pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK ... END PGP PUBLIC KEY BLOCK-----';
|
||||
var privkey = '-----BEGIN PGP PRIVATE KEY BLOCK ... END PGP PRIVATE KEY BLOCK-----'; //encrypted private key
|
||||
var passphrase = 'secret passphrase'; //what the privKey is encrypted with
|
||||
|
||||
var privKeyObj = openpgp.key.readArmored(privkey).keys[0];
|
||||
privKeyObj.decrypt(passphrase);
|
||||
```
|
||||
|
||||
```js
|
||||
options = {
|
||||
data: 'Hello, World!', // input as String (or Uint8Array)
|
||||
privateKeys: privKeyObj, // for signing
|
||||
detached: true
|
||||
};
|
||||
|
||||
openpgp.sign(options).then(function(signed) {
|
||||
cleartext = signed.data;
|
||||
detachedSig = signed.signature;
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
options = {
|
||||
message: openpgp.cleartext.readArmored(cleartext), // parse armored message
|
||||
signature: openpgp.signature.readArmored(detachedSig), // parse detached signature
|
||||
publicKeys: openpgp.key.readArmored(pubkey).keys // for verification
|
||||
};
|
||||
|
||||
openpgp.verify(options).then(function(verified) {
|
||||
validity = verified.signatures[0].valid; // true
|
||||
if (validity) {
|
||||
console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
A jsdoc build of our code comments is available at [doc/index.html](http://openpgpjs.org/openpgpjs/doc/index.html). Public calls should generally be made through the OpenPGP object [doc/openpgp.html](http://openpgpjs.org/openpgpjs/doc/module-openpgp.html).
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openpgp",
|
||||
"version": "2.3.2",
|
||||
"version": "2.5.13",
|
||||
"license": "LGPL-3.0+",
|
||||
"homepage": "http://openpgpjs.org/",
|
||||
"authors": [
|
||||
|
2183
dist/openpgp.js
vendored
2183
dist/openpgp.js
vendored
File diff suppressed because it is too large
Load Diff
20
dist/openpgp.min.js
vendored
20
dist/openpgp.min.js
vendored
File diff suppressed because one or more lines are too long
10
dist/openpgp.worker.js
vendored
10
dist/openpgp.worker.js
vendored
@ -45,7 +45,7 @@ self.onmessage = function(event) {
|
||||
break;
|
||||
|
||||
default:
|
||||
delegate(msg.event, msg.options || {});
|
||||
delegate(msg.id, msg.event, msg.options || {});
|
||||
}
|
||||
};
|
||||
|
||||
@ -76,18 +76,18 @@ function seedRandom(buffer) {
|
||||
* @param {String} method The public api function to be delegated to the worker thread
|
||||
* @param {Object} options The api function's options
|
||||
*/
|
||||
function delegate(method, options) {
|
||||
function delegate(id, method, options) {
|
||||
if (typeof openpgp[method] !== 'function') {
|
||||
response({ event:'method-return', err:'Unknown Worker Event' });
|
||||
response({ id:id, event:'method-return', err:'Unknown Worker Event' });
|
||||
return;
|
||||
}
|
||||
// parse cloned packets
|
||||
options = openpgp.packet.clone.parseClonedPackets(options, method);
|
||||
openpgp[method](options).then(function(data) {
|
||||
// clone packets (for web worker structured cloning algorithm)
|
||||
response({ event:'method-return', data:openpgp.packet.clone.clonePackets(data) });
|
||||
response({ id:id, event:'method-return', data:openpgp.packet.clone.clonePackets(data) });
|
||||
}).catch(function(e) {
|
||||
response({ event:'method-return', err:e.message });
|
||||
response({ id:id, event:'method-return', err:e.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
2
dist/openpgp.worker.min.js
vendored
2
dist/openpgp.worker.min.js
vendored
@ -1 +1 @@
|
||||
/*! OpenPGP.js v2.3.2 - 2016-06-08 - this is LGPL licensed code, see LICENSE/our website http://openpgpjs.org/ for more information. */!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){for(var b in a)h.config[b]=a[b]}function e(a){a instanceof Uint8Array||(a=new Uint8Array(a)),h.crypto.random.randomBuffer.set(a)}function f(a,b){return"function"!=typeof h[a]?void g({event:"method-return",err:"Unknown Worker Event"}):(b=h.packet.clone.parseClonedPackets(b,a),void h[a](b).then(function(a){g({event:"method-return",data:h.packet.clone.clonePackets(a)})})["catch"](function(a){g({event:"method-return",err:a.message})}))}function g(a){h.crypto.random.randomBuffer.size<i&&self.postMessage({event:"request-seed"}),self.postMessage(a,h.util.getTransferables.call(h.util,a.data))}self.window={},importScripts("openpgp.min.js");var h=window.openpgp,i=4e4,j=6e4;h.crypto.random.randomBuffer.init(j),self.onmessage=function(a){var b=a.data||{};switch(b.event){case"configure":d(b.config);break;case"seed-random":e(b.buf);break;default:f(b.event,b.options||{})}}},{}]},{},[1]);
|
||||
/*! OpenPGP.js v2.5.13 - 2017-11-06 - this is LGPL licensed code, see LICENSE/our website http://openpgpjs.org/ for more information. */!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){for(var b in a)h.config[b]=a[b]}function e(a){a instanceof Uint8Array||(a=new Uint8Array(a)),h.crypto.random.randomBuffer.set(a)}function f(a,b,c){return"function"!=typeof h[b]?void g({id:a,event:"method-return",err:"Unknown Worker Event"}):(c=h.packet.clone.parseClonedPackets(c,b),void h[b](c).then(function(b){g({id:a,event:"method-return",data:h.packet.clone.clonePackets(b)})})["catch"](function(b){g({id:a,event:"method-return",err:b.message})}))}function g(a){h.crypto.random.randomBuffer.size<i&&self.postMessage({event:"request-seed"}),self.postMessage(a,h.util.getTransferables.call(h.util,a.data))}self.window={},importScripts("openpgp.min.js");var h=window.openpgp,i=4e4,j=6e4;h.crypto.random.randomBuffer.init(j),self.onmessage=function(a){var b=a.data||{};switch(b.event){case"configure":d(b.config);break;case"seed-random":e(b.buf);break;default:f(b.id,b.event,b.options||{})}}},{}]},{},[1]);
|
2
npm-shrinkwrap.json
generated
2
npm-shrinkwrap.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openpgp",
|
||||
"version": "2.3.2",
|
||||
"version": "2.5.13",
|
||||
"dependencies": {
|
||||
"asmcrypto-lite": {
|
||||
"version": "1.1.0",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "openpgp",
|
||||
"description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.",
|
||||
"version": "2.3.2",
|
||||
"version": "2.5.13",
|
||||
"license": "LGPL-3.0+",
|
||||
"homepage": "http://openpgpjs.org/",
|
||||
"engines": {
|
||||
@ -35,6 +35,7 @@
|
||||
"asmcrypto-lite": "^1.0.0",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babelify": "^7.2.0",
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"chai": "~3.5.0",
|
||||
"es6-promise": "^3.1.2",
|
||||
"grunt": "~0.4.5",
|
||||
|
@ -29,23 +29,26 @@ import config from './config';
|
||||
import packet from './packet';
|
||||
import enums from './enums.js';
|
||||
import armor from './encoding/armor.js';
|
||||
import * as sigModule from './signature.js';
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Class that represents an OpenPGP cleartext signed message.
|
||||
* See {@link http://tools.ietf.org/html/rfc4880#section-7}
|
||||
* @param {String} text The cleartext of the signed message
|
||||
* @param {module:packet/packetlist} packetlist The packetlist with signature packets or undefined
|
||||
* if message not yet signed
|
||||
* @param {module:signature} signature The detached signature or an empty signature if message not yet signed
|
||||
*/
|
||||
|
||||
export function CleartextMessage(text, packetlist) {
|
||||
export function CleartextMessage(text, signature) {
|
||||
if (!(this instanceof CleartextMessage)) {
|
||||
return new CleartextMessage(text, packetlist);
|
||||
return new CleartextMessage(text, signature);
|
||||
}
|
||||
// normalize EOL to canonical form <CR><LF>
|
||||
this.text = text.replace(/\r/g, '').replace(/[\t ]+\n/g, "\n").replace(/\n/g,"\r\n");
|
||||
this.packets = packetlist || new packet.List();
|
||||
if (signature && !(signature instanceof sigModule.Signature)) {
|
||||
throw new Error('Invalid signature input');
|
||||
}
|
||||
this.signature = signature || new sigModule.Signature(new packet.List());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,7 +57,7 @@ export function CleartextMessage(text, packetlist) {
|
||||
*/
|
||||
CleartextMessage.prototype.getSigningKeyIds = function() {
|
||||
var keyIds = [];
|
||||
var signatureList = this.packets.filterByTag(enums.packet.signature);
|
||||
var signatureList = this.signature.packets;
|
||||
signatureList.forEach(function(packet) {
|
||||
keyIds.push(packet.issuerKeyId);
|
||||
});
|
||||
@ -64,8 +67,18 @@ CleartextMessage.prototype.getSigningKeyIds = function() {
|
||||
/**
|
||||
* Sign the cleartext message
|
||||
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
|
||||
* @return {module:message~CleartextMessage} new cleartext message with signed content
|
||||
*/
|
||||
CleartextMessage.prototype.sign = function(privateKeys) {
|
||||
return new CleartextMessage(this.text, this.signDetached(privateKeys));
|
||||
};
|
||||
|
||||
/**
|
||||
* Sign the cleartext message
|
||||
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
|
||||
* @return {module:signature~Signature} new detached signature of message content
|
||||
*/
|
||||
CleartextMessage.prototype.signDetached = function(privateKeys) {
|
||||
var packetlist = new packet.List();
|
||||
var literalDataPacket = new packet.Literal();
|
||||
literalDataPacket.setText(this.text);
|
||||
@ -84,7 +97,7 @@ CleartextMessage.prototype.sign = function(privateKeys) {
|
||||
signaturePacket.sign(signingKeyPacket, literalDataPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
}
|
||||
this.packets = packetlist;
|
||||
return new sigModule.Signature(packetlist);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -93,8 +106,17 @@ CleartextMessage.prototype.sign = function(privateKeys) {
|
||||
* @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
CleartextMessage.prototype.verify = function(keys) {
|
||||
return this.verifyDetached(this.signature, keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify signatures of cleartext signed message
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify signatures
|
||||
* @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
CleartextMessage.prototype.verifyDetached = function(signature, keys) {
|
||||
var result = [];
|
||||
var signatureList = this.packets.filterByTag(enums.packet.signature);
|
||||
var signatureList = signature.packets;
|
||||
var literalDataPacket = new packet.Literal();
|
||||
// we assume that cleartext signature is generated based on UTF8 cleartext
|
||||
literalDataPacket.setText(this.text);
|
||||
@ -115,6 +137,11 @@ CleartextMessage.prototype.verify = function(keys) {
|
||||
verifiedSig.keyid = signatureList[i].issuerKeyId;
|
||||
verifiedSig.valid = null;
|
||||
}
|
||||
|
||||
var packetlist = new packet.List();
|
||||
packetlist.push(signatureList[i]);
|
||||
verifiedSig.signature = new sigModule.Signature(packetlist);
|
||||
|
||||
result.push(verifiedSig);
|
||||
}
|
||||
return result;
|
||||
@ -137,7 +164,7 @@ CleartextMessage.prototype.armor = function() {
|
||||
var body = {
|
||||
hash: enums.read(enums.hash, config.prefer_hash_algorithm).toUpperCase(),
|
||||
text: this.text,
|
||||
data: this.packets.write()
|
||||
data: this.signature.packets.write()
|
||||
};
|
||||
return armor.encode(enums.armor.signed, body);
|
||||
};
|
||||
@ -157,7 +184,8 @@ export function readArmored(armoredText) {
|
||||
var packetlist = new packet.List();
|
||||
packetlist.read(input.data);
|
||||
verifyHeaders(input.headers, packetlist);
|
||||
var newMessage = new CleartextMessage(input.text, packetlist);
|
||||
var signature = new sigModule.Signature(packetlist);
|
||||
var newMessage = new CleartextMessage(input.text, signature);
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
|
@ -40,14 +40,16 @@ export default {
|
||||
aead_protect: false, // use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption
|
||||
integrity_protect: true, // use integrity protection for symmetric encryption
|
||||
ignore_mdc_error: false, // fail on decrypt if message is not integrity protected
|
||||
checksum_required: false, // do not throw error when armor is missing a checksum
|
||||
rsa_blinding: true,
|
||||
use_native: true, // use native node.js crypto and Web Crypto apis (if available)
|
||||
zero_copy: false, // use transferable objects between the Web Worker and main thread
|
||||
debug: false,
|
||||
tolerant: true, // ignore unsupported/unrecognizable packets instead of throwing an error
|
||||
show_version: true,
|
||||
show_comment: true,
|
||||
versionstring: "OpenPGP.js VERSION",
|
||||
commentstring: "http://openpgpjs.org",
|
||||
commentstring: "https://openpgpjs.org",
|
||||
keyserver: "https://keyserver.ubuntu.com",
|
||||
node_store: './openpgp.store'
|
||||
};
|
||||
|
@ -153,7 +153,10 @@ export default function RSA() {
|
||||
keyGenOpt = {
|
||||
name: 'RSA-OAEP',
|
||||
modulusLength: B, // the specified keysize in bits
|
||||
publicExponent: Euint8.subarray(0, 3) // take three bytes (max 65537)
|
||||
publicExponent: Euint8.subarray(0, 3), // take three bytes (max 65537)
|
||||
hash: {
|
||||
name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify'
|
||||
}
|
||||
};
|
||||
keys = webCrypto.generateKey(keyGenOpt, true, ['encrypt', 'decrypt']);
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import config from '../config';
|
||||
* 3 = PGP MESSAGE
|
||||
* 4 = PUBLIC KEY BLOCK
|
||||
* 5 = PRIVATE KEY BLOCK
|
||||
* 6 = SIGNATURE
|
||||
*/
|
||||
function getType(text) {
|
||||
var reHeader = /^-----BEGIN PGP (MESSAGE, PART \d+\/\d+|MESSAGE, PART \d+|SIGNED MESSAGE|MESSAGE|PUBLIC KEY BLOCK|PRIVATE KEY BLOCK|SIGNATURE)-----$\n/m;
|
||||
@ -62,10 +63,7 @@ function getType(text) {
|
||||
return enums.armor.multipart_last;
|
||||
|
||||
} else
|
||||
// BEGIN PGP SIGNATURE
|
||||
// Used for detached signatures, OpenPGP/MIME signatures, and
|
||||
// cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE
|
||||
// for detached signatures.
|
||||
// BEGIN PGP SIGNED MESSAGE
|
||||
if (/SIGNED MESSAGE/.test(header[1])) {
|
||||
return enums.armor.signed;
|
||||
|
||||
@ -86,6 +84,14 @@ function getType(text) {
|
||||
// Used for armoring private keys.
|
||||
if (/PRIVATE KEY BLOCK/.test(header[1])) {
|
||||
return enums.armor.private_key;
|
||||
|
||||
} else
|
||||
// BEGIN PGP SIGNATURE
|
||||
// Used for detached signatures, OpenPGP/MIME signatures, and
|
||||
// cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE
|
||||
// for detached signatures.
|
||||
if (/SIGNATURE/.test(header[1])) {
|
||||
return enums.armor.signature;
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,30 +181,9 @@ var crc_table = [
|
||||
|
||||
function createcrc24(input) {
|
||||
var crc = 0xB704CE;
|
||||
var index = 0;
|
||||
|
||||
while ((input.length - index) > 16) {
|
||||
for (var index = 0; index < input.length; index++) {
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 1]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 2]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 3]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 4]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 5]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 6]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 7]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 8]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 9]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 10]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 11]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 12]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 13]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 14]) & 0xff];
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index + 15]) & 0xff];
|
||||
index += 16;
|
||||
}
|
||||
|
||||
for (var j = index; j < input.length; j++) {
|
||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index++]) & 0xff];
|
||||
}
|
||||
return crc & 0xffffff;
|
||||
}
|
||||
@ -206,8 +191,7 @@ function createcrc24(input) {
|
||||
/**
|
||||
* Splits a message into two parts, the headers and the body. This is an internal function
|
||||
* @param {String} text OpenPGP armored message part
|
||||
* @returns {(Boolean|Object)} Either false in case of an error
|
||||
* or an object with attribute "headers" containing the headers and
|
||||
* @returns {Object} An object with attribute "headers" containing the headers
|
||||
* and an attribute "body" containing the body.
|
||||
*/
|
||||
function splitHeaders(text) {
|
||||
@ -240,29 +224,31 @@ function splitHeaders(text) {
|
||||
*/
|
||||
function verifyHeaders(headers) {
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
if (!/^(Version|Comment|MessageID|Hash|Charset): .+$/.test(headers[i])) {
|
||||
if (!/^[^:\s]+: .+$/.test(headers[i])) {
|
||||
throw new Error('Improperly formatted armor header: ' + headers[i]);
|
||||
}
|
||||
if (config.debug && !/^(Version|Comment|MessageID|Hash|Charset): .+$/.test(headers[i])) {
|
||||
console.log('Unknown header: ' + headers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a message into two parts, the body and the checksum. This is an internal function
|
||||
* @param {String} text OpenPGP armored message part
|
||||
* @returns {(Boolean|Object)} Either false in case of an error
|
||||
* or an object with attribute "body" containing the body
|
||||
* @returns {Object} An object with attribute "body" containing the body
|
||||
* and an attribute "checksum" containing the checksum.
|
||||
*/
|
||||
function splitChecksum(text) {
|
||||
var reChecksumStart = /^=/m;
|
||||
text = text.trim();
|
||||
var body = text;
|
||||
var checksum = "";
|
||||
|
||||
var matchResult = reChecksumStart.exec(text);
|
||||
var lastEquals = text.lastIndexOf("=");
|
||||
|
||||
if (matchResult !== null) {
|
||||
body = text.slice(0, matchResult.index);
|
||||
checksum = text.slice(matchResult.index + 1);
|
||||
if (lastEquals >= 0 && lastEquals !== text.length - 1) { // '=' as the last char means no checksum
|
||||
body = text.slice(0, lastEquals);
|
||||
checksum = text.slice(lastEquals + 1).substr(0, 4);
|
||||
}
|
||||
|
||||
return { body: body, checksum: checksum };
|
||||
@ -284,6 +270,7 @@ function dearmor(text) {
|
||||
|
||||
var type = getType(text);
|
||||
|
||||
text = text.trim() + "\n";
|
||||
var splittext = text.split(reSplit);
|
||||
|
||||
// IE has a bug in split with a re. If the pattern matches the beginning of the
|
||||
@ -325,12 +312,9 @@ function dearmor(text) {
|
||||
checksum = sig_sum.checksum;
|
||||
}
|
||||
|
||||
checksum = checksum.substr(0, 4);
|
||||
|
||||
if (!verifyCheckSum(result.data, checksum)) {
|
||||
throw new Error("Ascii armor integrity check on message failed: '" +
|
||||
checksum +
|
||||
"' should be '" +
|
||||
if (!verifyCheckSum(result.data, checksum) && (checksum || config.checksum_required)) {
|
||||
// will NOT throw error if checksum is empty AND checksum is not required (GPG compatibility)
|
||||
throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" +
|
||||
getCheckSum(result.data) + "'");
|
||||
}
|
||||
|
||||
@ -397,6 +381,13 @@ function armor(messagetype, body, partindex, parttotal) {
|
||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
||||
result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
|
||||
break;
|
||||
case enums.armor.signature:
|
||||
result.push("-----BEGIN PGP SIGNATURE-----\r\n");
|
||||
result.push(addheader());
|
||||
result.push(base64.encode(body));
|
||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
||||
result.push("-----END PGP SIGNATURE-----\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
|
@ -297,7 +297,8 @@ export default {
|
||||
signed: 2,
|
||||
message: 3,
|
||||
public_key: 4,
|
||||
private_key: 5
|
||||
private_key: 5,
|
||||
signature: 6
|
||||
},
|
||||
|
||||
/** Asserts validity and converts from string/integer to integer. */
|
||||
|
@ -26,6 +26,13 @@ export * from './openpgp';
|
||||
import * as keyMod from './key';
|
||||
export const key = keyMod;
|
||||
|
||||
/**
|
||||
* @see module:signature
|
||||
* @name module:openpgp.signature
|
||||
*/
|
||||
import * as signatureMod from './signature';
|
||||
export const signature = signatureMod;
|
||||
|
||||
/**
|
||||
* @see module:message
|
||||
* @name module:openpgp.message
|
||||
|
450
src/key.js
450
src/key.js
@ -130,7 +130,7 @@ Key.prototype.packetlist2structure = function(packetlist) {
|
||||
util.print_debug('Dropping subkey binding signature without preceding subkey packet');
|
||||
continue;
|
||||
}
|
||||
subKey.bindingSignature = packetlist[i];
|
||||
subKey.bindingSignatures.push(packetlist[i]);
|
||||
break;
|
||||
case enums.signature.key_revocation:
|
||||
this.revocationSignature = packetlist[i];
|
||||
@ -358,7 +358,7 @@ Key.prototype.getEncryptionKeyPacket = function() {
|
||||
}
|
||||
// if no valid subkey for encryption, evaluate primary key
|
||||
var primaryUser = this.getPrimaryUser();
|
||||
if (primaryUser &&
|
||||
if (primaryUser && primaryUser.selfCertificate && !primaryUser.selfCertificate.isExpired() &&
|
||||
isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate)) {
|
||||
return this.primaryKey;
|
||||
}
|
||||
@ -511,7 +511,7 @@ Key.prototype.getPrimaryUser = function() {
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < this.users[i].selfCertifications.length; j++) {
|
||||
primUser.push({user: this.users[i], selfCertificate: this.users[i].selfCertifications[j]});
|
||||
primUser.push({index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j]});
|
||||
}
|
||||
}
|
||||
// sort by primary user flag and signature creation time
|
||||
@ -638,6 +638,64 @@ Key.prototype.revoke = function() {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Signs primary user of key
|
||||
* @param {Array<module:key~Key>} privateKey decrypted private keys for signing
|
||||
* @return {module:key~Key} new public key with new certificate signature
|
||||
*/
|
||||
Key.prototype.signPrimaryUser = function(privateKeys) {
|
||||
var {index, user} = this.getPrimaryUser() || {};
|
||||
if (!user) {
|
||||
throw new Error('Could not find primary user');
|
||||
}
|
||||
user = user.sign(this.primaryKey, privateKeys);
|
||||
var key = new Key(this.toPacketlist());
|
||||
key.users[index] = user;
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Signs all users of key
|
||||
* @param {Array<module:key~Key>} privateKeys decrypted private keys for signing
|
||||
* @return {module:key~Key} new public key with new certificate signature
|
||||
*/
|
||||
Key.prototype.signAllUsers = function(privateKeys) {
|
||||
var users = this.users.map(user => user.sign(this.primaryKey, privateKeys));
|
||||
var key = new Key(this.toPacketlist());
|
||||
key.users = users;
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies primary user of key
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
|
||||
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
Key.prototype.verifyPrimaryUser = function(keys) {
|
||||
var {user} = this.getPrimaryUser() || {};
|
||||
if (!user) {
|
||||
throw new Error('Could not find primary user');
|
||||
}
|
||||
return user.verifyAllSignatures(this.primaryKey, keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies all users of key
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
|
||||
* @return {Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>} list of userid, signer's keyid and validity of signature
|
||||
*/
|
||||
Key.prototype.verifyAllUsers = function(keys) {
|
||||
return this.users.reduce((signatures, user) => {
|
||||
return signatures.concat(
|
||||
user.verifyAllSignatures(this.primaryKey, keys).map(signature => ({
|
||||
userid: user.userId.userid,
|
||||
keyid: signature.keyid,
|
||||
valid: signature.valid
|
||||
}))
|
||||
);
|
||||
}, []);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Class that represents an user ID or attribute packet and the relevant signatures.
|
||||
@ -727,6 +785,66 @@ User.prototype.isValidSelfCertificate = function(primaryKey, selfCertificate) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Signs user
|
||||
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
|
||||
* @param {Array<module:key~Key>} privateKeys decrypted private keys for signing
|
||||
* @return {module:key~Key} new user with new certificate signatures
|
||||
*/
|
||||
User.prototype.sign = function(primaryKey, privateKeys) {
|
||||
var user, dataToSign, signingKeyPacket, signaturePacket;
|
||||
dataToSign = {};
|
||||
dataToSign.key = primaryKey;
|
||||
dataToSign.userid = this.userId || this.userAttribute;
|
||||
user = new User(this.userId || this.userAttribute);
|
||||
user.otherCertifications = [];
|
||||
privateKeys.forEach(privateKey => {
|
||||
if (privateKey.isPublic()) {
|
||||
throw new Error('Need private key for signing');
|
||||
}
|
||||
if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) {
|
||||
throw new Error('Not implemented for self signing');
|
||||
}
|
||||
signingKeyPacket = privateKey.getSigningKeyPacket();
|
||||
if (!signingKeyPacket) {
|
||||
throw new Error('Could not find valid signing key packet');
|
||||
}
|
||||
if (!signingKeyPacket.isDecrypted) {
|
||||
throw new Error('Private key is not decrypted.');
|
||||
}
|
||||
signaturePacket = new packet.Signature();
|
||||
// Most OpenPGP implementations use generic certification (0x10)
|
||||
signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic);
|
||||
signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data];
|
||||
signaturePacket.hashAlgorithm = privateKey.getPreferredHashAlgorithm();
|
||||
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
||||
signaturePacket.signingKeyId = signingKeyPacket.getKeyId();
|
||||
signaturePacket.sign(signingKeyPacket, dataToSign);
|
||||
user.otherCertifications.push(signaturePacket);
|
||||
});
|
||||
user.update(this, primaryKey);
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies all user signatures
|
||||
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
|
||||
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
User.prototype.verifyAllSignatures = function(primaryKey, keys) {
|
||||
var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey };
|
||||
var certificates = this.selfCertifications.concat(this.otherCertifications || []);
|
||||
return certificates.map(signaturePacket => {
|
||||
var keyPackets = keys.filter(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId));
|
||||
var valid = null;
|
||||
if (keyPackets.length > 0) {
|
||||
valid = keyPackets.some(keyPacket => signaturePacket.verify(keyPacket.primaryKey, dataToVerify));
|
||||
}
|
||||
return { keyid: signaturePacket.issuerKeyId, valid: valid };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify User. Checks for existence of self signatures, revocation signatures
|
||||
* and validity of self signature
|
||||
@ -785,7 +903,7 @@ function SubKey(subKeyPacket) {
|
||||
return new SubKey(subKeyPacket);
|
||||
}
|
||||
this.subKey = subKeyPacket;
|
||||
this.bindingSignature = null;
|
||||
this.bindingSignatures = [];
|
||||
this.revocationSignature = null;
|
||||
}
|
||||
|
||||
@ -797,7 +915,9 @@ SubKey.prototype.toPacketlist = function() {
|
||||
var packetlist = new packet.List();
|
||||
packetlist.push(this.subKey);
|
||||
packetlist.push(this.revocationSignature);
|
||||
packetlist.push(this.bindingSignature);
|
||||
for(var i = 0; i < this.bindingSignatures.length; i++) {
|
||||
packetlist.push(this.bindingSignatures[i]);
|
||||
}
|
||||
return packetlist;
|
||||
};
|
||||
|
||||
@ -807,8 +927,15 @@ SubKey.prototype.toPacketlist = function() {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
SubKey.prototype.isValidEncryptionKey = function(primaryKey) {
|
||||
return this.verify(primaryKey) === enums.keyStatus.valid &&
|
||||
isValidEncryptionKeyPacket(this.subKey, this.bindingSignature);
|
||||
if(this.verify(primaryKey) !== enums.keyStatus.valid) {
|
||||
return false;
|
||||
}
|
||||
for(var i = 0; i < this.bindingSignatures.length; i++) {
|
||||
if(isValidEncryptionKeyPacket(this.subKey, this.bindingSignatures[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -817,8 +944,15 @@ SubKey.prototype.isValidEncryptionKey = function(primaryKey) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
SubKey.prototype.isValidSigningKey = function(primaryKey) {
|
||||
return this.verify(primaryKey) === enums.keyStatus.valid &&
|
||||
isValidSigningKeyPacket(this.subKey, this.bindingSignature);
|
||||
if(this.verify(primaryKey) !== enums.keyStatus.valid) {
|
||||
return false;
|
||||
}
|
||||
for(var i = 0; i < this.bindingSignatures.length; i++) {
|
||||
if(isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -838,24 +972,39 @@ SubKey.prototype.verify = function(primaryKey) {
|
||||
Date.now() > (this.subKey.created.getTime() + this.subKey.expirationTimeV3*24*3600*1000)) {
|
||||
return enums.keyStatus.expired;
|
||||
}
|
||||
// check subkey binding signature
|
||||
if (!this.bindingSignature) {
|
||||
return enums.keyStatus.invalid;
|
||||
// check subkey binding signatures (at least one valid binding sig needed)
|
||||
for(var i = 0; i < this.bindingSignatures.length; i++) {
|
||||
var isLast = (i === this.bindingSignatures.length - 1);
|
||||
var sig = this.bindingSignatures[i];
|
||||
// check binding signature is not expired
|
||||
if(sig.isExpired()) {
|
||||
if(isLast) {
|
||||
return enums.keyStatus.expired; // last expired binding signature
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// check binding signature can verify
|
||||
if (!(sig.verified || sig.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) {
|
||||
if(isLast) {
|
||||
return enums.keyStatus.invalid; // last invalid binding signature
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// check V4 expiration time
|
||||
if (this.subKey.version === 4) {
|
||||
if(sig.keyNeverExpires === false && Date.now() > (this.subKey.created.getTime() + sig.keyExpirationTime*1000)) {
|
||||
if(isLast) {
|
||||
return enums.keyStatus.expired; // last V4 expired binding signature
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return enums.keyStatus.valid; // found a binding signature that passed all checks
|
||||
}
|
||||
if (this.bindingSignature.isExpired()) {
|
||||
return enums.keyStatus.expired;
|
||||
}
|
||||
if (!(this.bindingSignature.verified ||
|
||||
this.bindingSignature.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) {
|
||||
return enums.keyStatus.invalid;
|
||||
}
|
||||
// check V4 expiration time
|
||||
if (this.subKey.version === 4 &&
|
||||
this.bindingSignature.keyNeverExpires === false &&
|
||||
Date.now() > (this.subKey.created.getTime() + this.bindingSignature.keyExpirationTime*1000)) {
|
||||
return enums.keyStatus.expired;
|
||||
}
|
||||
return enums.keyStatus.valid;
|
||||
return enums.keyStatus.invalid; // no binding signatures to check
|
||||
};
|
||||
|
||||
/**
|
||||
@ -863,7 +1012,17 @@ SubKey.prototype.verify = function(primaryKey) {
|
||||
* @return {Date|null}
|
||||
*/
|
||||
SubKey.prototype.getExpirationTime = function() {
|
||||
return getExpirationTime(this.subKey, this.bindingSignature);
|
||||
var highest;
|
||||
for(var i = 0; i < this.bindingSignatures.length; i++) {
|
||||
var current = getExpirationTime(this.subKey, this.bindingSignatures[i]);
|
||||
if(current === null) {
|
||||
return null;
|
||||
}
|
||||
if(!highest || current > highest) {
|
||||
highest = current;
|
||||
}
|
||||
}
|
||||
return highest;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -883,11 +1042,14 @@ SubKey.prototype.update = function(subKey, primaryKey) {
|
||||
subKey.subKey.tag === enums.packet.secretSubkey) {
|
||||
this.subKey = subKey.subKey;
|
||||
}
|
||||
// binding signature
|
||||
if (!this.bindingSignature && subKey.bindingSignature &&
|
||||
(subKey.bindingSignature.verified ||
|
||||
subKey.bindingSignature.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) {
|
||||
this.bindingSignature = subKey.bindingSignature;
|
||||
// update missing binding signatures
|
||||
if(this.bindingSignatures.length < subKey.bindingSignatures.length) {
|
||||
for(var i = this.bindingSignatures.length; i < subKey.bindingSignatures.length; i++) {
|
||||
var newSig = subKey.bindingSignatures[i];
|
||||
if (newSig.verified || newSig.verify(primaryKey, {key: primaryKey, bind: this.subKey})) {
|
||||
this.bindingSignatures.push(newSig);
|
||||
}
|
||||
}
|
||||
}
|
||||
// revocation signature
|
||||
if (!this.revocationSignature && subKey.revocationSignature && !subKey.revocationSignature.isExpired() &&
|
||||
@ -898,24 +1060,20 @@ SubKey.prototype.update = function(subKey, primaryKey) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads an OpenPGP armored text and returns one or multiple key objects
|
||||
* @param {String} armoredText text to be parsed
|
||||
* Reads an unarmored OpenPGP key list and returns one or multiple key objects
|
||||
* @param {Uint8Array} data to be parsed
|
||||
* @return {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays
|
||||
* @static
|
||||
*/
|
||||
export function readArmored(armoredText) {
|
||||
export function read(data) {
|
||||
var result = {};
|
||||
result.keys = [];
|
||||
try {
|
||||
var input = armor.decode(armoredText);
|
||||
if (!(input.type === enums.armor.public_key || input.type === enums.armor.private_key)) {
|
||||
throw new Error('Armored text not of type key');
|
||||
}
|
||||
var packetlist = new packet.List();
|
||||
packetlist.read(input.data);
|
||||
packetlist.read(data);
|
||||
var keyIndex = packetlist.indexOfTag(enums.packet.publicKey, enums.packet.secretKey);
|
||||
if (keyIndex.length === 0) {
|
||||
throw new Error('No key packet found in armored text');
|
||||
throw new Error('No key packet found');
|
||||
}
|
||||
for (var i = 0; i < keyIndex.length; i++) {
|
||||
var oneKeyList = packetlist.slice(keyIndex[i], keyIndex[i + 1]);
|
||||
@ -934,6 +1092,26 @@ export function readArmored(armoredText) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an OpenPGP armored text and returns one or multiple key objects
|
||||
* @param {String} armoredText text to be parsed
|
||||
* @return {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays
|
||||
* @static
|
||||
*/
|
||||
export function readArmored(armoredText) {
|
||||
try {
|
||||
var input = armor.decode(armoredText);
|
||||
if (!(input.type === enums.armor.public_key || input.type === enums.armor.private_key)) {
|
||||
throw new Error('Armored text not of type key');
|
||||
}
|
||||
return read(input.data);
|
||||
} catch (e) {
|
||||
var result = {keys: [], err: []};
|
||||
result.err.push(e);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new OpenPGP key. Currently only supports RSA keys.
|
||||
* Primary and subkey will be of same type.
|
||||
@ -944,11 +1122,12 @@ export function readArmored(armoredText) {
|
||||
If array is used, the first userId is set as primary user Id
|
||||
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key
|
||||
* @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked
|
||||
* @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires
|
||||
* @return {module:key~Key}
|
||||
* @static
|
||||
*/
|
||||
export function generate(options) {
|
||||
var packetlist, secretKeyPacket, userIdPacket, dataToSign, signaturePacket, secretSubkeyPacket, subkeySignaturePacket;
|
||||
var secretKeyPacket, secretSubkeyPacket;
|
||||
return Promise.resolve().then(() => {
|
||||
options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
|
||||
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
|
||||
@ -962,7 +1141,9 @@ export function generate(options) {
|
||||
options.userIds = [options.userIds];
|
||||
}
|
||||
|
||||
return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(wrapKeyObject);
|
||||
return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(() => {
|
||||
return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options);
|
||||
});
|
||||
});
|
||||
|
||||
function generateSecretKey() {
|
||||
@ -976,80 +1157,127 @@ export function generate(options) {
|
||||
secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.keyType);
|
||||
return secretSubkeyPacket.generate(options.numBits);
|
||||
}
|
||||
}
|
||||
|
||||
function wrapKeyObject() {
|
||||
// set passphrase protection
|
||||
if (options.passphrase) {
|
||||
secretKeyPacket.encrypt(options.passphrase);
|
||||
secretSubkeyPacket.encrypt(options.passphrase);
|
||||
/**
|
||||
* Reformats and signs an OpenPGP with a given User ID. Currently only supports RSA keys.
|
||||
* @param {module:key~Key} options.privateKey The private key to reformat
|
||||
* @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign]
|
||||
* @param {String|Array<String>} options.userIds assumes already in form of "User Name <username@email.com>"
|
||||
If array is used, the first userId is set as primary user Id
|
||||
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key
|
||||
* @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked
|
||||
* @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires
|
||||
* @return {module:key~Key}
|
||||
* @static
|
||||
*/
|
||||
export function reformat(options) {
|
||||
var secretKeyPacket, secretSubkeyPacket;
|
||||
return Promise.resolve().then(() => {
|
||||
|
||||
options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
|
||||
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
|
||||
throw new Error('Only RSA Encrypt or Sign supported');
|
||||
}
|
||||
|
||||
packetlist = new packet.List();
|
||||
|
||||
packetlist.push(secretKeyPacket);
|
||||
|
||||
options.userIds.forEach(function(userId, index) {
|
||||
|
||||
userIdPacket = new packet.Userid();
|
||||
userIdPacket.read(util.str2Uint8Array(userId));
|
||||
|
||||
dataToSign = {};
|
||||
dataToSign.userid = userIdPacket;
|
||||
dataToSign.key = secretKeyPacket;
|
||||
signaturePacket = new packet.Signature();
|
||||
signaturePacket.signatureType = enums.signature.cert_generic;
|
||||
signaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data];
|
||||
signaturePacket.preferredSymmetricAlgorithms = [];
|
||||
// prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support)
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes256);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes128);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.cast5);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.tripledes);
|
||||
signaturePacket.preferredHashAlgorithms = [];
|
||||
// prefer fast asm.js implementations (SHA-256, SHA-1)
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha256);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha1);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha512);
|
||||
signaturePacket.preferredCompressionAlgorithms = [];
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zlib);
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zip);
|
||||
if (index === 0) {
|
||||
signaturePacket.isPrimaryUserID = true;
|
||||
}
|
||||
if (config.integrity_protect) {
|
||||
signaturePacket.features = [];
|
||||
signaturePacket.features.push(1); // Modification Detection
|
||||
}
|
||||
signaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(userIdPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
|
||||
});
|
||||
|
||||
dataToSign = {};
|
||||
dataToSign.key = secretKeyPacket;
|
||||
dataToSign.bind = secretSubkeyPacket;
|
||||
subkeySignaturePacket = new packet.Signature();
|
||||
subkeySignaturePacket.signatureType = enums.signature.subkey_binding;
|
||||
subkeySignaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
subkeySignaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage];
|
||||
subkeySignaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(secretSubkeyPacket);
|
||||
packetlist.push(subkeySignaturePacket);
|
||||
|
||||
if (!options.unlocked) {
|
||||
secretKeyPacket.clearPrivateMPIs();
|
||||
secretSubkeyPacket.clearPrivateMPIs();
|
||||
if (!options.passphrase) { // Key without passphrase is unlocked by definition
|
||||
options.unlocked = true;
|
||||
}
|
||||
if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') {
|
||||
options.userIds = [options.userIds];
|
||||
}
|
||||
var packetlist = options.privateKey.toPacketlist();
|
||||
for (var i = 0; i < packetlist.length; i++) {
|
||||
if (packetlist[i].tag === enums.packet.secretKey) {
|
||||
secretKeyPacket = packetlist[i];
|
||||
} else if (packetlist[i].tag === enums.packet.secretSubkey) {
|
||||
secretSubkeyPacket = packetlist[i];
|
||||
}
|
||||
}
|
||||
return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options);
|
||||
});
|
||||
}
|
||||
|
||||
return new Key(packetlist);
|
||||
function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
|
||||
// set passphrase protection
|
||||
if (options.passphrase) {
|
||||
secretKeyPacket.encrypt(options.passphrase);
|
||||
secretSubkeyPacket.encrypt(options.passphrase);
|
||||
}
|
||||
|
||||
var packetlist = new packet.List();
|
||||
|
||||
packetlist.push(secretKeyPacket);
|
||||
|
||||
options.userIds.forEach(function(userId, index) {
|
||||
|
||||
var userIdPacket = new packet.Userid();
|
||||
userIdPacket.read(util.str2Uint8Array(userId));
|
||||
|
||||
var dataToSign = {};
|
||||
dataToSign.userid = userIdPacket;
|
||||
dataToSign.key = secretKeyPacket;
|
||||
var signaturePacket = new packet.Signature();
|
||||
signaturePacket.signatureType = enums.signature.cert_generic;
|
||||
signaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data];
|
||||
signaturePacket.preferredSymmetricAlgorithms = [];
|
||||
// prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support)
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes256);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes128);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.cast5);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.tripledes);
|
||||
signaturePacket.preferredHashAlgorithms = [];
|
||||
// prefer fast asm.js implementations (SHA-256). SHA-1 will not be secure much longer...move to bottom of list
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha256);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha512);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha1);
|
||||
signaturePacket.preferredCompressionAlgorithms = [];
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zlib);
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zip);
|
||||
if (index === 0) {
|
||||
signaturePacket.isPrimaryUserID = true;
|
||||
}
|
||||
if (config.integrity_protect) {
|
||||
signaturePacket.features = [];
|
||||
signaturePacket.features.push(1); // Modification Detection
|
||||
}
|
||||
if (options.keyExpirationTime > 0) {
|
||||
signaturePacket.keyExpirationTime = options.keyExpirationTime;
|
||||
signaturePacket.keyNeverExpires = false;
|
||||
}
|
||||
signaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(userIdPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
|
||||
});
|
||||
|
||||
var dataToSign = {};
|
||||
dataToSign.key = secretKeyPacket;
|
||||
dataToSign.bind = secretSubkeyPacket;
|
||||
var subkeySignaturePacket = new packet.Signature();
|
||||
subkeySignaturePacket.signatureType = enums.signature.subkey_binding;
|
||||
subkeySignaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
subkeySignaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage];
|
||||
if (options.keyExpirationTime > 0) {
|
||||
subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime;
|
||||
subkeySignaturePacket.keyNeverExpires = false;
|
||||
}
|
||||
subkeySignaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(secretSubkeyPacket);
|
||||
packetlist.push(subkeySignaturePacket);
|
||||
|
||||
if (!options.unlocked) {
|
||||
secretKeyPacket.clearPrivateMPIs();
|
||||
secretSubkeyPacket.clearPrivateMPIs();
|
||||
}
|
||||
|
||||
return new Key(packetlist);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +32,7 @@ import LocalStore from './localstore.js';
|
||||
* Initialization routine for the keyring. This method reads the
|
||||
* keyring from HTML5 local storage and initializes this instance.
|
||||
* @constructor
|
||||
* @param {class} [storeHandler] class implementing load() and store() methods
|
||||
* @param {class} [storeHandler] class implementing loadPublic(), loadPrivate(), storePublic(), and storePrivate() methods
|
||||
*/
|
||||
export default function Keyring(storeHandler) {
|
||||
this.storeHandler = storeHandler || new LocalStore();
|
||||
|
114
src/message.js
114
src/message.js
@ -32,6 +32,7 @@ import enums from './enums.js';
|
||||
import armor from './encoding/armor.js';
|
||||
import config from './config';
|
||||
import crypto from './crypto';
|
||||
import * as sigModule from './signature.js';
|
||||
import * as keyModule from './key.js';
|
||||
|
||||
/**
|
||||
@ -291,10 +292,11 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) {
|
||||
|
||||
/**
|
||||
* Sign the message (the literal data packet of the message)
|
||||
* @param {Array<module:key~Key>} privateKey private keys with decrypted secret key data for signing
|
||||
* @return {module:message~Message} new message with signed content
|
||||
* @param {Array<module:key~Key>} privateKey private keys with decrypted secret key data for signing
|
||||
* @param {Signature} signature (optional) any existing detached signature to add to the message
|
||||
* @return {module:message~Message} new message with signed content
|
||||
*/
|
||||
Message.prototype.sign = function(privateKeys) {
|
||||
Message.prototype.sign = function(privateKeys=[], signature=null) {
|
||||
|
||||
var packetlist = new packet.List();
|
||||
|
||||
@ -306,12 +308,30 @@ Message.prototype.sign = function(privateKeys) {
|
||||
var literalFormat = enums.write(enums.literal, literalDataPacket.format);
|
||||
var signatureType = literalFormat === enums.literal.binary ?
|
||||
enums.signature.binary : enums.signature.text;
|
||||
var i, signingKeyPacket;
|
||||
var i, signingKeyPacket, existingSigPacketlist, onePassSig;
|
||||
|
||||
if (signature) {
|
||||
existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
|
||||
if (existingSigPacketlist.length) {
|
||||
for (i = existingSigPacketlist.length - 1; i >= 0; i--) {
|
||||
var sigPacket = existingSigPacketlist[i];
|
||||
onePassSig = new packet.OnePassSignature();
|
||||
onePassSig.type = signatureType;
|
||||
onePassSig.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
onePassSig.publicKeyAlgorithm = sigPacket.publicKeyAlgorithm;
|
||||
onePassSig.signingKeyId = sigPacket.issuerKeyId;
|
||||
if (!privateKeys.length && i === 0) {
|
||||
onePassSig.flags = 1;
|
||||
}
|
||||
packetlist.push(onePassSig);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < privateKeys.length; i++) {
|
||||
if (privateKeys[i].isPublic()) {
|
||||
throw new Error('Need private key for signing');
|
||||
}
|
||||
var onePassSig = new packet.OnePassSignature();
|
||||
onePassSig = new packet.OnePassSignature();
|
||||
onePassSig.type = signatureType;
|
||||
//TODO get preferred hashg algo from key signature
|
||||
onePassSig.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
@ -321,6 +341,9 @@ Message.prototype.sign = function(privateKeys) {
|
||||
}
|
||||
onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
||||
onePassSig.signingKeyId = signingKeyPacket.getKeyId();
|
||||
if (i === privateKeys.length - 1) {
|
||||
onePassSig.flags = 1;
|
||||
}
|
||||
packetlist.push(onePassSig);
|
||||
}
|
||||
|
||||
@ -338,22 +361,93 @@ Message.prototype.sign = function(privateKeys) {
|
||||
packetlist.push(signaturePacket);
|
||||
}
|
||||
|
||||
if (signature) {
|
||||
packetlist.concat(existingSigPacketlist);
|
||||
}
|
||||
|
||||
return new Message(packetlist);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a detached signature for the message (the literal data packet of the message)
|
||||
* @param {Array<module:key~Key>} privateKey private keys with decrypted secret key data for signing
|
||||
* @param {Signature} signature (optional) any existing detached signature
|
||||
* @return {module:signature~Signature} new detached signature of message content
|
||||
*/
|
||||
Message.prototype.signDetached = function(privateKeys=[], signature=null) {
|
||||
|
||||
var packetlist = new packet.List();
|
||||
|
||||
var literalDataPacket = this.packets.findPacket(enums.packet.literal);
|
||||
if (!literalDataPacket) {
|
||||
throw new Error('No literal data packet to sign.');
|
||||
}
|
||||
|
||||
var literalFormat = enums.write(enums.literal, literalDataPacket.format);
|
||||
var signatureType = literalFormat === enums.literal.binary ?
|
||||
enums.signature.binary : enums.signature.text;
|
||||
|
||||
for (var i = 0; i < privateKeys.length; i++) {
|
||||
var signingKeyPacket = privateKeys[i].getSigningKeyPacket();
|
||||
var signaturePacket = new packet.Signature();
|
||||
signaturePacket.signatureType = signatureType;
|
||||
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
||||
if (!signingKeyPacket.isDecrypted) {
|
||||
throw new Error('Private key is not decrypted.');
|
||||
}
|
||||
signaturePacket.sign(signingKeyPacket, literalDataPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
}
|
||||
if (signature) {
|
||||
var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
|
||||
packetlist.concat(existingSigPacketlist);
|
||||
}
|
||||
|
||||
return new sigModule.Signature(packetlist);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify message signatures
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify signatures
|
||||
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
Message.prototype.verify = function(keys) {
|
||||
var result = [];
|
||||
var msg = this.unwrapCompressed();
|
||||
var literalDataList = msg.packets.filterByTag(enums.packet.literal);
|
||||
if (literalDataList.length !== 1) {
|
||||
throw new Error('Can only verify message with one literal data packet.');
|
||||
}
|
||||
var signatureList = msg.packets.filterByTag(enums.packet.signature);
|
||||
return createVerificationObjects(signatureList, literalDataList, keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify detached message signature
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify signatures
|
||||
* @param {Signature}
|
||||
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
Message.prototype.verifyDetached = function(signature, keys) {
|
||||
var msg = this.unwrapCompressed();
|
||||
var literalDataList = msg.packets.filterByTag(enums.packet.literal);
|
||||
if (literalDataList.length !== 1) {
|
||||
throw new Error('Can only verify message with one literal data packet.');
|
||||
}
|
||||
var signatureList = signature.packets;
|
||||
return createVerificationObjects(signatureList, literalDataList, keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create list of objects containing signer's keyid and validity of signature
|
||||
* @param {Array<module:packet/signature>} signatureList array of signature packets
|
||||
* @param {Array<module:packet/literal>} literalDataList array of literal data packets
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify signatures
|
||||
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
function createVerificationObjects(signatureList, literalDataList, keys) {
|
||||
var result = [];
|
||||
for (var i = 0; i < signatureList.length; i++) {
|
||||
var keyPacket = null;
|
||||
for (var j = 0; j < keys.length; j++) {
|
||||
@ -365,16 +459,22 @@ Message.prototype.verify = function(keys) {
|
||||
|
||||
var verifiedSig = {};
|
||||
if (keyPacket) {
|
||||
//found a key packet that matches keyId of signature
|
||||
verifiedSig.keyid = signatureList[i].issuerKeyId;
|
||||
verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataList[0]);
|
||||
} else {
|
||||
verifiedSig.keyid = signatureList[i].issuerKeyId;
|
||||
verifiedSig.valid = null;
|
||||
}
|
||||
|
||||
var packetlist = new packet.List();
|
||||
packetlist.push(signatureList[i]);
|
||||
verifiedSig.signature = new sigModule.Signature(packetlist);
|
||||
|
||||
result.push(verifiedSig);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap compressed message
|
||||
|
182
src/openpgp.js
182
src/openpgp.js
@ -92,12 +92,13 @@ export function destroyWorker() {
|
||||
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
|
||||
* @param {Number} numBits (optional) number of bits for the key creation. (should be 2048 or 4096)
|
||||
* @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked
|
||||
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires
|
||||
* @return {Promise<Object>} The generated key object in the form:
|
||||
* { key:Key, privateKeyArmored:String, publicKeyArmored:String }
|
||||
* @static
|
||||
*/
|
||||
export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false } = {}) {
|
||||
const options = formatUserIds({ userIds, passphrase, numBits, unlocked });
|
||||
export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0 } = {}) {
|
||||
const options = formatUserIds({ userIds, passphrase, numBits, unlocked, keyExpirationTime });
|
||||
|
||||
if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||
return asyncProxy.delegate('generateKey', options);
|
||||
@ -112,6 +113,32 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal
|
||||
})).catch(onError.bind(null, 'Error generating keypair'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reformats signature packets for a key and rewraps key object.
|
||||
* @param {Array<Object>} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
|
||||
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
|
||||
* @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked
|
||||
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires
|
||||
* @return {Promise<Object>} The generated key object in the form:
|
||||
* { key:Key, privateKeyArmored:String, publicKeyArmored:String }
|
||||
* @static
|
||||
*/
|
||||
export function reformatKey({ privateKey, userIds=[], passphrase="", unlocked=false, keyExpirationTime=0 } = {}) {
|
||||
const options = formatUserIds({ privateKey, userIds, passphrase, unlocked, keyExpirationTime });
|
||||
|
||||
if (asyncProxy) {
|
||||
return asyncProxy.delegate('reformatKey', options);
|
||||
}
|
||||
|
||||
return key.reformat(options).then(newKey => ({
|
||||
|
||||
key: newKey,
|
||||
privateKeyArmored: newKey.armor(),
|
||||
publicKeyArmored: newKey.toPublic().armor()
|
||||
|
||||
})).catch(onError.bind(null, 'Error reformatting keypair'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a private key with your passphrase.
|
||||
* @param {Key} privateKey the private key that is to be decrypted
|
||||
@ -151,36 +178,48 @@ export function decryptKey({ privateKey, passphrase }) {
|
||||
* @param {Key|Array<Key>} privateKeys (optional) private keys for signing. If omitted message will not be signed
|
||||
* @param {String|Array<String>} passwords (optional) array of passwords or a single password to encrypt the message
|
||||
* @param {String} filename (optional) a filename for the literal data packet
|
||||
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
|
||||
* @return {Promise<String|Message>} encrypted ASCII armored message, or the full Message object if 'armor' is false
|
||||
* @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects
|
||||
* @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object)
|
||||
* @param {Signature} signature (optional) a detached signature to add to the encrypted message
|
||||
* @return {Promise<Object>} encrypted (and optionally signed message) in the form:
|
||||
* {data: ASCII armored message if 'armor' is true,
|
||||
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
||||
* @static
|
||||
*/
|
||||
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true }) {
|
||||
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false, signature=null }) {
|
||||
checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
|
||||
|
||||
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||
return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor });
|
||||
return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor, detached, signature });
|
||||
}
|
||||
|
||||
var result = {};
|
||||
return Promise.resolve().then(() => {
|
||||
|
||||
let message = createMessage(data, filename);
|
||||
if (privateKeys) { // sign the message only if private keys are specified
|
||||
message = message.sign(privateKeys);
|
||||
if (!privateKeys) {
|
||||
privateKeys = [];
|
||||
}
|
||||
if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified
|
||||
if (detached) {
|
||||
var detachedSignature = message.signDetached(privateKeys, signature);
|
||||
if (armor) {
|
||||
result.signature = detachedSignature.armor();
|
||||
} else {
|
||||
result.signature = detachedSignature;
|
||||
}
|
||||
} else {
|
||||
message = message.sign(privateKeys, signature);
|
||||
}
|
||||
}
|
||||
return message.encrypt(publicKeys, passwords);
|
||||
|
||||
}).then(message => {
|
||||
|
||||
if(armor) {
|
||||
return {
|
||||
data: message.armor()
|
||||
};
|
||||
if (armor) {
|
||||
result.data = message.armor();
|
||||
} else {
|
||||
result.message = message;
|
||||
}
|
||||
return {
|
||||
message: message
|
||||
};
|
||||
|
||||
return result;
|
||||
}).catch(onError.bind(null, 'Error encrypting message'));
|
||||
}
|
||||
|
||||
@ -193,22 +232,31 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
|
||||
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String }
|
||||
* @param {String} password (optional) single password to decrypt the message
|
||||
* @param {String} format (optional) return data format either as 'utf8' or 'binary'
|
||||
* @param {Signature} signature (optional) detached signature for verification
|
||||
* @return {Promise<Object>} decrypted and verified message in the form:
|
||||
* { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
|
||||
* @static
|
||||
*/
|
||||
export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8' }) {
|
||||
export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8', signature=null }) {
|
||||
checkMessage(message); publicKeys = toArray(publicKeys);
|
||||
|
||||
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||
return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format });
|
||||
return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format, signature });
|
||||
}
|
||||
|
||||
return message.decrypt(privateKey, sessionKey, password).then(message => {
|
||||
|
||||
const result = parseMessage(message, format);
|
||||
if (publicKeys && result.data) { // verify only if publicKeys are specified
|
||||
result.signatures = message.verify(publicKeys);
|
||||
if (result.data) { // verify
|
||||
if (!publicKeys) {
|
||||
publicKeys = [];
|
||||
}
|
||||
if (signature) {
|
||||
//detached signature
|
||||
result.signatures = message.verifyDetached(signature, publicKeys);
|
||||
} else {
|
||||
result.signatures = message.verify(publicKeys);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@ -225,33 +273,50 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password,
|
||||
|
||||
/**
|
||||
* Signs a cleartext message.
|
||||
* @param {String} data cleartext input to be signed
|
||||
* @param {String | Uint8Array} data cleartext input to be signed
|
||||
* @param {Key|Array<Key>} privateKeys array of keys or single key with decrypted secret key data to sign cleartext
|
||||
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
|
||||
* @return {Promise<String|CleartextMessage>} ASCII armored message or the message of type CleartextMessage
|
||||
* @param {Boolean} detached (optional) if the return value should contain a detached signature
|
||||
* @return {Promise<Object>} signed cleartext in the form:
|
||||
* {data: ASCII armored message if 'armor' is true,
|
||||
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
||||
* @static
|
||||
*/
|
||||
export function sign({ data, privateKeys, armor=true }) {
|
||||
checkString(data);
|
||||
export function sign({ data, privateKeys, armor=true, detached=false}) {
|
||||
checkData(data);
|
||||
privateKeys = toArray(privateKeys);
|
||||
|
||||
if (asyncProxy) { // use web worker if available
|
||||
return asyncProxy.delegate('sign', { data, privateKeys, armor });
|
||||
return asyncProxy.delegate('sign', { data, privateKeys, armor, detached });
|
||||
}
|
||||
|
||||
var result = {};
|
||||
return execute(() => {
|
||||
var message;
|
||||
|
||||
const cleartextMessage = new cleartext.CleartextMessage(data);
|
||||
cleartextMessage.sign(privateKeys);
|
||||
|
||||
if(armor) {
|
||||
return {
|
||||
data: cleartextMessage.armor()
|
||||
};
|
||||
if (util.isString(data)) {
|
||||
message = new cleartext.CleartextMessage(data);
|
||||
} else {
|
||||
message = messageLib.fromBinary(data);
|
||||
}
|
||||
return {
|
||||
message: cleartextMessage
|
||||
};
|
||||
|
||||
if (detached) {
|
||||
var signature = message.signDetached(privateKeys);
|
||||
if (armor) {
|
||||
result.signature = signature.armor();
|
||||
} else {
|
||||
result.signature = signature;
|
||||
}
|
||||
} else {
|
||||
message = message.sign(privateKeys);
|
||||
}
|
||||
|
||||
if (armor) {
|
||||
result.data = message.armor();
|
||||
} else {
|
||||
result.message = message;
|
||||
}
|
||||
return result;
|
||||
|
||||
}, 'Error signing cleartext message');
|
||||
}
|
||||
@ -260,24 +325,35 @@ export function sign({ data, privateKeys, armor=true }) {
|
||||
* Verifies signatures of cleartext signed message
|
||||
* @param {Key|Array<Key>} publicKeys array of publicKeys or single key, to verify signatures
|
||||
* @param {CleartextMessage} message cleartext message object with signatures
|
||||
* @param {Signature} signature (optional) detached signature for verification
|
||||
* @return {Promise<Object>} cleartext with status of verified signatures in the form of:
|
||||
* { data:String, signatures: [{ keyid:String, valid:Boolean }] }
|
||||
* @static
|
||||
*/
|
||||
export function verify({ message, publicKeys }) {
|
||||
checkCleartextMessage(message);
|
||||
export function verify({ message, publicKeys, signature=null }) {
|
||||
checkCleartextOrMessage(message);
|
||||
publicKeys = toArray(publicKeys);
|
||||
|
||||
if (asyncProxy) { // use web worker if available
|
||||
return asyncProxy.delegate('verify', { message, publicKeys });
|
||||
return asyncProxy.delegate('verify', { message, publicKeys, signature });
|
||||
}
|
||||
|
||||
return execute(() => ({
|
||||
var result = {};
|
||||
return execute(() => {
|
||||
if (cleartext.CleartextMessage.prototype.isPrototypeOf(message)) {
|
||||
result.data = message.getText();
|
||||
} else {
|
||||
result.data = message.getLiteralData();
|
||||
}
|
||||
if (signature) {
|
||||
//detached signature
|
||||
result.signatures = message.verifyDetached(signature, publicKeys);
|
||||
} else {
|
||||
result.signatures = message.verify(publicKeys);
|
||||
}
|
||||
return result;
|
||||
|
||||
data: message.getText(),
|
||||
signatures: message.verify(publicKeys)
|
||||
|
||||
}), 'Error verifying cleartext signed message');
|
||||
}, 'Error verifying cleartext signed message');
|
||||
}
|
||||
|
||||
|
||||
@ -299,7 +375,7 @@ export function verify({ message, publicKeys }) {
|
||||
* @static
|
||||
*/
|
||||
export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) {
|
||||
checkbinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords);
|
||||
checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords);
|
||||
|
||||
if (asyncProxy) { // use web worker if available
|
||||
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, publicKeys, passwords });
|
||||
@ -349,7 +425,7 @@ function checkString(data, name) {
|
||||
throw new Error('Parameter [' + (name || 'data') + '] must be of type String');
|
||||
}
|
||||
}
|
||||
function checkbinary(data, name) {
|
||||
function checkBinary(data, name) {
|
||||
if (!util.isUint8Array(data)) {
|
||||
throw new Error('Parameter [' + (name || 'data') + '] must be of type Uint8Array');
|
||||
}
|
||||
@ -364,9 +440,9 @@ function checkMessage(message) {
|
||||
throw new Error('Parameter [message] needs to be of type Message');
|
||||
}
|
||||
}
|
||||
function checkCleartextMessage(message) {
|
||||
if (!cleartext.CleartextMessage.prototype.isPrototypeOf(message)) {
|
||||
throw new Error('Parameter [message] needs to be of type CleartextMessage');
|
||||
function checkCleartextOrMessage(message) {
|
||||
if (!cleartext.CleartextMessage.prototype.isPrototypeOf(message) && !messageLib.Message.prototype.isPrototypeOf(message)) {
|
||||
throw new Error('Parameter [message] needs to be of type Message or CleartextMessage');
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,7 +467,11 @@ function formatUserIds(options) {
|
||||
if (!util.isString(id.name) || (id.email && !util.isEmailAddress(id.email))) {
|
||||
throw new Error('Invalid user id format');
|
||||
}
|
||||
return id.name + ' <' + id.email + '>';
|
||||
id.name = id.name.trim();
|
||||
if (id.name.length > 0) {
|
||||
id.name += ' ';
|
||||
}
|
||||
return id.name + '<' + id.email + '>';
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
import * as key from '../key.js';
|
||||
import * as message from '../message.js';
|
||||
import * as cleartext from '../cleartext.js';
|
||||
import * as signature from '../signature.js'
|
||||
import Packetlist from './packetlist.js';
|
||||
import type_keyid from '../type/keyid.js';
|
||||
|
||||
@ -55,9 +56,27 @@ export function clonePackets(options) {
|
||||
if (options.key) {
|
||||
options.key = options.key.toPacketlist();
|
||||
}
|
||||
if (options.message) {
|
||||
//could be either a Message or CleartextMessage object
|
||||
if (options.message instanceof message.Message) {
|
||||
options.message = options.message.packets;
|
||||
} else if (options.message instanceof cleartext.CleartextMessage) {
|
||||
options.message.signature = options.message.signature.packets;
|
||||
}
|
||||
}
|
||||
if (options.signature && (options.signature instanceof signature.Signature)) {
|
||||
options.signature = options.signature.packets;
|
||||
}
|
||||
if (options.signatures) {
|
||||
options.signatures = options.signatures.map(sig => verificationObjectToClone(sig));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function verificationObjectToClone(verObject) {
|
||||
verObject.signature = verObject.signature.packets;
|
||||
return verObject;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// //
|
||||
@ -85,13 +104,16 @@ export function parseClonedPackets(options, method) {
|
||||
if (options.key) {
|
||||
options.key = packetlistCloneToKey(options.key);
|
||||
}
|
||||
if (options.message && (method === 'sign' || method === 'verify')) { // sign and verify support only CleartextMessage
|
||||
if (options.message && options.message.signature) {
|
||||
options.message = packetlistCloneToCleartextMessage(options.message);
|
||||
} else if (options.message) {
|
||||
options.message = packetlistCloneToMessage(options.message);
|
||||
}
|
||||
if (options.signatures) {
|
||||
options.signatures = options.signatures.map(packetlistCloneToSignature);
|
||||
options.signatures = options.signatures.map(packetlistCloneToSignatures);
|
||||
}
|
||||
if (options.signature) {
|
||||
options.signature = packetlistCloneToSignature(options.signature);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
@ -102,16 +124,27 @@ function packetlistCloneToKey(clone) {
|
||||
}
|
||||
|
||||
function packetlistCloneToMessage(clone) {
|
||||
const packetlist = Packetlist.fromStructuredClone(clone.packets);
|
||||
const packetlist = Packetlist.fromStructuredClone(clone);
|
||||
return new message.Message(packetlist);
|
||||
}
|
||||
|
||||
function packetlistCloneToCleartextMessage(clone) {
|
||||
var packetlist = Packetlist.fromStructuredClone(clone.packets);
|
||||
return new cleartext.CleartextMessage(clone.text, packetlist);
|
||||
var packetlist = Packetlist.fromStructuredClone(clone.signature);
|
||||
return new cleartext.CleartextMessage(clone.text, new signature.Signature(packetlist));
|
||||
}
|
||||
|
||||
//verification objects
|
||||
function packetlistCloneToSignatures(clone) {
|
||||
clone.keyid = type_keyid.fromClone(clone.keyid);
|
||||
clone.signature = new signature.Signature(clone.signature);
|
||||
return clone;
|
||||
}
|
||||
|
||||
function packetlistCloneToSignature(clone) {
|
||||
clone.keyid = type_keyid.fromClone(clone.keyid);
|
||||
return clone;
|
||||
if (typeof clone === "string") {
|
||||
//signature is armored
|
||||
return clone;
|
||||
}
|
||||
var packetlist = Packetlist.fromStructuredClone(clone);
|
||||
return new signature.Signature(packetlist);
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ Compressed.prototype.decompress = function () {
|
||||
throw new Error('Compression algorithm BZip2 [BZ2] is not implemented.');
|
||||
|
||||
default:
|
||||
throw new Error("Compression algorithm unknown :" + this.alogrithm);
|
||||
throw new Error("Compression algorithm unknown :" + this.algorithm);
|
||||
}
|
||||
|
||||
this.packets.read(decompressed);
|
||||
|
@ -97,9 +97,9 @@ export default {
|
||||
if (length < 256) {
|
||||
return new Uint8Array([0x80 | (tag_type << 2), length]);
|
||||
} else if (length < 65536) {
|
||||
return util.concatUint8Array([0x80 | (tag_type << 2) | 1, util.writeNumber(length, 2)]);
|
||||
return util.concatUint8Array([new Uint8Array([0x80 | (tag_type << 2) | 1]), util.writeNumber(length, 2)]);
|
||||
} else {
|
||||
return util.concatUint8Array([0x80 | (tag_type << 2) | 2, util.writeNumber(length, 4)]);
|
||||
return util.concatUint8Array([new Uint8Array([0x80 | (tag_type << 2) | 2]), util.writeNumber(length, 4)]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -15,6 +15,7 @@ import util from '../util';
|
||||
import packetParser from './packet.js';
|
||||
import * as packets from './all_packets.js';
|
||||
import enums from '../enums.js';
|
||||
import config from '../config';
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
@ -44,6 +45,9 @@ Packetlist.prototype.read = function (bytes) {
|
||||
pushed = true;
|
||||
packet.read(parsed.packet);
|
||||
} catch(e) {
|
||||
if (!config.tolerant || parsed.tag == enums.packet.symmetricallyEncrypted || parsed.tag == enums.packet.literal || parsed.tag == enums.packet.compressed) {
|
||||
throw e;
|
||||
}
|
||||
if (pushed) {
|
||||
this.pop(); // drop unsupported packet
|
||||
}
|
||||
|
@ -269,6 +269,7 @@ SecretKey.prototype.decrypt = function (passphrase) {
|
||||
}
|
||||
this.mpi = this.mpi.concat(parsedMPI);
|
||||
this.isDecrypted = true;
|
||||
this.encrypted = null;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -187,9 +187,13 @@ Signature.prototype.write = function () {
|
||||
switch (this.version) {
|
||||
case 3:
|
||||
arr.push(new Uint8Array([3, 5])); // version, One-octet length of following hashed material. MUST be 5
|
||||
arr.push(this.signatureData);
|
||||
arr.push(new Uint8Array([this.signatureType]));
|
||||
arr.push(util.writeDate(this.created));
|
||||
arr.push(this.issuerKeyId.write());
|
||||
arr.push(new Uint8Array([this.publicKeyAlgorithm, this.hashAlgorithm]));
|
||||
arr.push(new Uint8Array([
|
||||
enums.write(enums.publicKey, this.publicKeyAlgorithm),
|
||||
enums.write(enums.hash, this.hashAlgorithm)
|
||||
]));
|
||||
break;
|
||||
case 4:
|
||||
arr.push(this.signatureData);
|
||||
@ -222,7 +226,17 @@ Signature.prototype.sign = function (key, data) {
|
||||
|
||||
var trailer = this.calculateTrailer();
|
||||
|
||||
var toHash = util.concatUint8Array([this.toSign(signatureType, data), this.signatureData, trailer]);
|
||||
var toHash = null;
|
||||
|
||||
switch (this.version) {
|
||||
case 3:
|
||||
toHash = util.concatUint8Array([this.toSign(signatureType, data), new Uint8Array([signatureType]), util.writeDate(this.created)]);
|
||||
break;
|
||||
case 4:
|
||||
toHash = util.concatUint8Array([this.toSign(signatureType, data), this.signatureData, trailer]);
|
||||
break;
|
||||
default: throw new Error('Version ' + this.version + ' of the signature is unsupported.');
|
||||
}
|
||||
|
||||
var hash = crypto.hash.digest(hashAlgorithm, toHash);
|
||||
|
||||
|
76
src/signature.js
Normal file
76
src/signature.js
Normal file
@ -0,0 +1,76 @@
|
||||
// GPG4Browsers - An OpenPGP implementation in javascript
|
||||
// Copyright (C) 2011 Recurity Labs GmbH
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 3.0 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
/**
|
||||
* @requires config
|
||||
* @requires crypto
|
||||
* @requires encoding/armor
|
||||
* @requires enums
|
||||
* @requires packet
|
||||
* @module signature
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import packet from './packet';
|
||||
import enums from './enums.js';
|
||||
import armor from './encoding/armor.js';
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Class that represents an OpenPGP signature.
|
||||
* @param {module:packet/packetlist} packetlist The signature packets
|
||||
*/
|
||||
|
||||
export function Signature(packetlist) {
|
||||
if (!(this instanceof Signature)) {
|
||||
return new Signature(packetlist);
|
||||
}
|
||||
this.packets = packetlist || new packet.List();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns ASCII armored text of signature
|
||||
* @return {String} ASCII armor
|
||||
*/
|
||||
Signature.prototype.armor = function() {
|
||||
return armor.encode(enums.armor.signature, this.packets.write());
|
||||
};
|
||||
|
||||
/**
|
||||
* reads an OpenPGP armored signature and returns a signature object
|
||||
* @param {String} armoredText text to be parsed
|
||||
* @return {Signature} new signature object
|
||||
* @static
|
||||
*/
|
||||
export function readArmored(armoredText) {
|
||||
var input = armor.decode(armoredText).data;
|
||||
return read(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* reads an OpenPGP signature as byte array and returns a signature object
|
||||
* @param {Uint8Array} input binary signature
|
||||
* @return {Signature} new signature object
|
||||
* @static
|
||||
*/
|
||||
export function read(input) {
|
||||
var packetlist = new packet.List();
|
||||
packetlist.read(input);
|
||||
return new Signature(packetlist);
|
||||
}
|
@ -189,7 +189,7 @@ S2K.prototype.produce_key = function (passphrase, numBytes) {
|
||||
}
|
||||
i = 0;
|
||||
|
||||
while (rlength <= numBytes) {
|
||||
while (rlength < numBytes) {
|
||||
var result = round(prefix.subarray(0,i), this);
|
||||
arr.push(result);
|
||||
rlength += result.length;
|
||||
|
16
src/util.js
16
src/util.js
@ -51,7 +51,7 @@ export default {
|
||||
if (!this.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
return / </.test(data) && />$/.test(data);
|
||||
return /</.test(data) && />$/.test(data);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -85,12 +85,9 @@ export default {
|
||||
|
||||
readNumber: function (bytes) {
|
||||
var n = 0;
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
n <<= 8;
|
||||
n += bytes[i];
|
||||
n += Math.pow(256, i) * bytes[bytes.length - 1 - i];
|
||||
}
|
||||
|
||||
return n;
|
||||
},
|
||||
|
||||
@ -274,9 +271,12 @@ export default {
|
||||
throw new Error('Uint8Array2str: Data must be in the form of a Uint8Array');
|
||||
}
|
||||
|
||||
var result = [];
|
||||
for (var i = 0; i < bin.length; i++) {
|
||||
result[i] = String.fromCharCode(bin[i]);
|
||||
var result = [],
|
||||
bs = 16384,
|
||||
j = bin.length;
|
||||
|
||||
for (var i = 0; i < j; i += bs) {
|
||||
result.push(String.fromCharCode.apply(String, bin.subarray(i, i+bs < j ? i+bs : j)));
|
||||
}
|
||||
return result.join('');
|
||||
},
|
||||
|
@ -39,13 +39,24 @@ export default function AsyncProxy({ path='openpgp.worker.js', worker, config }
|
||||
throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')');
|
||||
};
|
||||
this.seedRandom(INITIAL_RANDOM_SEED);
|
||||
// FIFO
|
||||
this.tasks = [];
|
||||
|
||||
if (config) {
|
||||
this.worker.postMessage({ event:'configure', config });
|
||||
}
|
||||
|
||||
// Cannot rely on task order being maintained, use object keyed by request ID to track tasks
|
||||
this.tasks = {};
|
||||
this.currentID = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get new request ID
|
||||
* @return {integer} New unique request ID
|
||||
*/
|
||||
AsyncProxy.prototype.getID = function() {
|
||||
return this.currentID++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Message handling
|
||||
*/
|
||||
@ -55,11 +66,12 @@ AsyncProxy.prototype.onMessage = function(event) {
|
||||
case 'method-return':
|
||||
if (msg.err) {
|
||||
// fail
|
||||
this.tasks.shift().reject(new Error(msg.err));
|
||||
this.tasks[msg.id].reject(new Error(msg.err));
|
||||
} else {
|
||||
// success
|
||||
this.tasks.shift().resolve(msg.data);
|
||||
this.tasks[msg.id].resolve(msg.data);
|
||||
}
|
||||
delete this.tasks[msg.id];
|
||||
break;
|
||||
case 'request-seed':
|
||||
this.seedRandom(RANDOM_SEED_REQUEST);
|
||||
@ -106,11 +118,13 @@ AsyncProxy.prototype.terminate = function() {
|
||||
* @return {Promise} see the corresponding public api functions for their return types
|
||||
*/
|
||||
AsyncProxy.prototype.delegate = function(method, options) {
|
||||
const id = this.getID();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// clone packets (for web worker structured cloning algorithm)
|
||||
this.worker.postMessage({ event:method, options:packet.clone.clonePackets(options) }, util.getTransferables.call(util, options));
|
||||
this.worker.postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables.call(util, options));
|
||||
|
||||
// remember to handle parsing cloned packets from worker
|
||||
this.tasks.push({ resolve: data => resolve(packet.clone.parseClonedPackets(data, method)), reject });
|
||||
this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(data, method)), reject };
|
||||
});
|
||||
};
|
||||
|
@ -44,7 +44,7 @@ self.onmessage = function(event) {
|
||||
break;
|
||||
|
||||
default:
|
||||
delegate(msg.event, msg.options || {});
|
||||
delegate(msg.id, msg.event, msg.options || {});
|
||||
}
|
||||
};
|
||||
|
||||
@ -75,18 +75,18 @@ function seedRandom(buffer) {
|
||||
* @param {String} method The public api function to be delegated to the worker thread
|
||||
* @param {Object} options The api function's options
|
||||
*/
|
||||
function delegate(method, options) {
|
||||
function delegate(id, method, options) {
|
||||
if (typeof openpgp[method] !== 'function') {
|
||||
response({ event:'method-return', err:'Unknown Worker Event' });
|
||||
response({ id:id, event:'method-return', err:'Unknown Worker Event' });
|
||||
return;
|
||||
}
|
||||
// parse cloned packets
|
||||
options = openpgp.packet.clone.parseClonedPackets(options, method);
|
||||
openpgp[method](options).then(function(data) {
|
||||
// clone packets (for web worker structured cloning algorithm)
|
||||
response({ event:'method-return', data:openpgp.packet.clone.clonePackets(data) });
|
||||
response({ id:id, event:'method-return', data:openpgp.packet.clone.clonePackets(data) });
|
||||
}).catch(function(e) {
|
||||
response({ event:'method-return', err:e.message });
|
||||
response({ id:id, event:'method-return', err:e.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ describe("ASCII armor", function() {
|
||||
var msg = getArmor(['Hash:SHA256']);
|
||||
msg = openpgp.cleartext.readArmored.bind(null, msg);
|
||||
expect(msg).to.throw(Error, /Improperly formatted armor header/);
|
||||
msg = getArmor(['<script>: SHA256']);
|
||||
msg = getArmor(['Ha sh: SHA256']);
|
||||
msg = openpgp.cleartext.readArmored.bind(null, msg);
|
||||
expect(msg).to.throw(Error, /Improperly formatted armor header/);
|
||||
msg = getArmor(['Hash SHA256']);
|
||||
@ -131,7 +131,7 @@ describe("ASCII armor", function() {
|
||||
expect(msg).to.throw(Error, /Unknown ASCII armor type/);
|
||||
});
|
||||
|
||||
it('Armor checksum validation', function () {
|
||||
it('Armor checksum validation - mismatch', function () {
|
||||
var privKey =
|
||||
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
|
||||
'Version: OpenPGP.js v0.3.0',
|
||||
@ -152,9 +152,140 @@ describe("ASCII armor", function() {
|
||||
'=wJN@',
|
||||
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n');
|
||||
|
||||
var result = openpgp.key.readArmored(privKey);
|
||||
expect(result.err).to.exist;
|
||||
expect(result.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
// try with default config
|
||||
var result_1 = openpgp.key.readArmored(privKey);
|
||||
expect(result_1.err).to.exist;
|
||||
expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
|
||||
// try opposite config
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
var result_2 = openpgp.key.readArmored(privKey);
|
||||
expect(result_2.err).to.exist;
|
||||
expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
|
||||
// back to default
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
});
|
||||
|
||||
it('Armor checksum validation - valid', function () {
|
||||
var privKey =
|
||||
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
|
||||
'Version: OpenPGP.js v0.3.0',
|
||||
'Comment: http://openpgpjs.org',
|
||||
'',
|
||||
'xbYEUubX7gEBANDWhzoP+Tr/IyRSv++vl5jBesQIPTYGQBdzF4YDnGEBABEB',
|
||||
'AAH+CQMIfzdw4/PKNl5gVXdtfDFdSIN8yJT2rbeg3+SsWexXZNNdRaONWaiB',
|
||||
'Z5cG9Q6+BoXKsEshIdcYOgwsAgRxlPpRA34Vvmg2QBk7PhdrkbK7aqENsJ1w',
|
||||
'dIlLD6p9GmLE20yVff58/fMiUtPRgsD83SpKTAX6EM1ulpkuQQNjmrVc5qc8',
|
||||
'7AMdF80JdW5kZWZpbmVkwj8EEAEIABMFAlLm1+4JEBD8MASZrpALAhsDAAAs',
|
||||
'QgD8CUrwv7Hrp/INR0/UvAvzS52VztREQwQWTJMrgTNHBGjHtgRS5tfuAQEA',
|
||||
'nys9SaSgR+l6iZc/M8hGIUmbuahE2/+mtw+/l0RO+WcAEQEAAf4JAwjr39Yi',
|
||||
'FzjxImDN1IoYVsonA9M+BtIIJHafuQUHjyEr1paJJK5xS6KlyGgpMTXTD6y/',
|
||||
'qxS3ZSPPzHGRrs2CmkVEiPmurn9Ed05tb0y9OnJkWtuh3z9VVq9d8zHzuENa',
|
||||
'bUfli+P/v+dRaZ+1rSOxUFbFYbFB5XK/A9b/OPFrv+mb4KrtLxugwj8EGAEI',
|
||||
'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq',
|
||||
'71vlXMJNXvoCeuejiRw=',
|
||||
'=wJNM',
|
||||
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n');
|
||||
|
||||
// try with default config
|
||||
var result_1 = openpgp.key.readArmored(privKey);
|
||||
expect(result_1.err).to.not.exist;
|
||||
|
||||
// try opposite config
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
var result_2 = openpgp.key.readArmored(privKey);
|
||||
expect(result_2.err).to.not.exist;
|
||||
|
||||
// back to default
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
});
|
||||
|
||||
it('Armor checksum validation - missing', function () {
|
||||
var privKeyNoCheckSum =
|
||||
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
|
||||
'Version: OpenPGP.js v0.3.0',
|
||||
'Comment: http://openpgpjs.org',
|
||||
'',
|
||||
'xbYEUubX7gEBANDWhzoP+Tr/IyRSv++vl5jBesQIPTYGQBdzF4YDnGEBABEB',
|
||||
'AAH+CQMIfzdw4/PKNl5gVXdtfDFdSIN8yJT2rbeg3+SsWexXZNNdRaONWaiB',
|
||||
'Z5cG9Q6+BoXKsEshIdcYOgwsAgRxlPpRA34Vvmg2QBk7PhdrkbK7aqENsJ1w',
|
||||
'dIlLD6p9GmLE20yVff58/fMiUtPRgsD83SpKTAX6EM1ulpkuQQNjmrVc5qc8',
|
||||
'7AMdF80JdW5kZWZpbmVkwj8EEAEIABMFAlLm1+4JEBD8MASZrpALAhsDAAAs',
|
||||
'QgD8CUrwv7Hrp/INR0/UvAvzS52VztREQwQWTJMrgTNHBGjHtgRS5tfuAQEA',
|
||||
'nys9SaSgR+l6iZc/M8hGIUmbuahE2/+mtw+/l0RO+WcAEQEAAf4JAwjr39Yi',
|
||||
'FzjxImDN1IoYVsonA9M+BtIIJHafuQUHjyEr1paJJK5xS6KlyGgpMTXTD6y/',
|
||||
'qxS3ZSPPzHGRrs2CmkVEiPmurn9Ed05tb0y9OnJkWtuh3z9VVq9d8zHzuENa',
|
||||
'bUfli+P/v+dRaZ+1rSOxUFbFYbFB5XK/A9b/OPFrv+mb4KrtLxugwj8EGAEI',
|
||||
'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq',
|
||||
'71vlXMJNXvoCeuejiRw=',
|
||||
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n');
|
||||
|
||||
// try with default config
|
||||
var result_1 = openpgp.key.readArmored(privKeyNoCheckSum);
|
||||
if(openpgp.config.checksum_required) {
|
||||
expect(result_1.err).to.exist;
|
||||
expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
} else {
|
||||
expect(result_1.err).to.not.exist;
|
||||
}
|
||||
|
||||
// try opposite config
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
var result_2 = openpgp.key.readArmored(privKeyNoCheckSum);
|
||||
if(openpgp.config.checksum_required) {
|
||||
expect(result_2.err).to.exist;
|
||||
expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
} else {
|
||||
expect(result_2.err).to.not.exist;
|
||||
}
|
||||
|
||||
// back to default
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
});
|
||||
|
||||
it('Armor checksum validation - missing - trailing newline', function () {
|
||||
var privKeyNoCheckSumWithTrailingNewline =
|
||||
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
|
||||
'Version: OpenPGP.js v0.3.0',
|
||||
'Comment: http://openpgpjs.org',
|
||||
'',
|
||||
'xbYEUubX7gEBANDWhzoP+Tr/IyRSv++vl5jBesQIPTYGQBdzF4YDnGEBABEB',
|
||||
'AAH+CQMIfzdw4/PKNl5gVXdtfDFdSIN8yJT2rbeg3+SsWexXZNNdRaONWaiB',
|
||||
'Z5cG9Q6+BoXKsEshIdcYOgwsAgRxlPpRA34Vvmg2QBk7PhdrkbK7aqENsJ1w',
|
||||
'dIlLD6p9GmLE20yVff58/fMiUtPRgsD83SpKTAX6EM1ulpkuQQNjmrVc5qc8',
|
||||
'7AMdF80JdW5kZWZpbmVkwj8EEAEIABMFAlLm1+4JEBD8MASZrpALAhsDAAAs',
|
||||
'QgD8CUrwv7Hrp/INR0/UvAvzS52VztREQwQWTJMrgTNHBGjHtgRS5tfuAQEA',
|
||||
'nys9SaSgR+l6iZc/M8hGIUmbuahE2/+mtw+/l0RO+WcAEQEAAf4JAwjr39Yi',
|
||||
'FzjxImDN1IoYVsonA9M+BtIIJHafuQUHjyEr1paJJK5xS6KlyGgpMTXTD6y/',
|
||||
'qxS3ZSPPzHGRrs2CmkVEiPmurn9Ed05tb0y9OnJkWtuh3z9VVq9d8zHzuENa',
|
||||
'bUfli+P/v+dRaZ+1rSOxUFbFYbFB5XK/A9b/OPFrv+mb4KrtLxugwj8EGAEI',
|
||||
'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq',
|
||||
'71vlXMJNXvoCeuejiRw=',
|
||||
'-----END PGP PRIVATE KEY BLOCK-----',
|
||||
''].join('\n');
|
||||
|
||||
// try with default config
|
||||
var result_1 = openpgp.key.readArmored(privKeyNoCheckSumWithTrailingNewline);
|
||||
if(openpgp.config.checksum_required) {
|
||||
expect(result_1.err).to.exist;
|
||||
expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
} else {
|
||||
expect(result_1.err).to.not.exist;
|
||||
}
|
||||
|
||||
// try opposite config
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
var result_2 = openpgp.key.readArmored(privKeyNoCheckSumWithTrailingNewline);
|
||||
if(openpgp.config.checksum_required) {
|
||||
expect(result_2.err).to.exist;
|
||||
expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/);
|
||||
} else {
|
||||
expect(result_2.err).to.not.exist;
|
||||
}
|
||||
|
||||
// back to default
|
||||
openpgp.config.checksum_required = !openpgp.config.checksum_required;
|
||||
});
|
||||
|
||||
it('Accept header with trailing whitespace', function () {
|
||||
|
@ -303,7 +303,7 @@ describe('Key', function() {
|
||||
'=MVfN',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||
|
||||
var pgp_desktop_pub =
|
||||
var pgp_desktop_pub =
|
||||
['-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||
'Version: Encryption Desktop 10.3.0 (Build 9307)',
|
||||
'',
|
||||
@ -342,7 +342,7 @@ var pgp_desktop_pub =
|
||||
'=dVeR',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||
|
||||
var pgp_desktop_priv =
|
||||
var pgp_desktop_priv =
|
||||
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
|
||||
'Version: Encryption Desktop 10.3.0 (Build 9307)',
|
||||
'',
|
||||
@ -527,7 +527,63 @@ var pgp_desktop_priv =
|
||||
'=Q/kB',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||
|
||||
it('Parsing armored text with RSA key and ECC subkey', function(done) {
|
||||
var valid_binding_sig_among_many_expired_sigs_pub = [
|
||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||
'',
|
||||
'mQENBFFHU9EBCAC2JjXCZOB43KUiU6V7bbMQWKFCWBmtaSTOgdEixUJKmaEyWbVd',
|
||||
'OYEx5HzyumUB0fbjPv4/sbLIddpS0WAiiC1VY8zPR+eZ7W4FhEq5qOEOX5BXt5HE',
|
||||
'B/eXJgB50B4n+/ld0+IZKJwEyTQlhQCr/HdQW9aPmroOdN6lJ7bxLmWLimZNZTeX',
|
||||
'0UdX8zD7fiocwEciFEMVRXmO2dRhJvw++yNqxEzjLdc4V62M4j1ZbxtUDEq7yiDS',
|
||||
'Xkyj0PGgnJN7ClBCHeUoLguVz9CWSXwvlcwUlq3Q1fn5mnVqGuC4a7WV540X9YUl',
|
||||
'I9PdP42sagQeoGRYBA6t/pP4shmDWekPPzOxABEBAAG0FU9wZW5QR1AgPG9wZW5A',
|
||||
'cGdwLmpzPokBTgQTAQgAOAIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgBYhBLgc',
|
||||
'q0yYY4G97CsGveVSFit0ImJRBQJZu/+XAAoJEOVSFit0ImJR5L4H/38kwavLzN1E',
|
||||
'eYeVw9qXj+sR35MYcJo97pFELYcE0bINZL/IinG4mx/HgHOIrLeCLO00L3TQetPq',
|
||||
'FNJw+5VVMurOeIgaRGptEckLH23Hr+LQKRx+fYpUiqHtaEWNMBcXUstSDN2zvrCx',
|
||||
'U1gjYn0dczlTqZLTYEnq3NuZP/fyBQQe/HvdY8TQEJ0w+DUGIt+GQuBvQmWBc60m',
|
||||
'693brCHSZSJJIl/U3Y8OP3MTees8R1vDpiSCdmKm3kyvIDkSA+nowbsjGPdK07VU',
|
||||
'hCdntlDWhYMHsudPUxFrPMF8QHo2EkIN4MfWIaVERtKIpVDNV2IgXDS8meEWN9fS',
|
||||
'4GdNsd7iYFW5AQ0EUUdT0QEIAKTi5PevnL5RB3944aVm/n2ID4QAktsUAE/0rIi/',
|
||||
'Dg+2HFpyd/m8uV+chQV4L965UjLzYgpvn/Hka+DhiwNKUt4IUcfrSUJ8hg9ZI/yQ',
|
||||
'ZjiqwEmUvUYBc/qbc5LdKMhlWhgySz7Kf7k7D5GlK56x8lPbfjLBlTU9VgFBRIot',
|
||||
'Joyczs5wbtHh0IVc0tW2oShup3yfxQQ3E76lzRF36TVNoXszUHNIiLHyBZbf3Oqs',
|
||||
'MXTVchjhnZBZ664dCqZ8anQma2jSvNHlCF3f2SvAv7uf0maIj5IYu0rPA0i6gapE',
|
||||
'I83g8dbSqF3YNIcF2NBbck2MWKD1GEUaLXW+A7nyIDXpeH8AEQEAAYkBPAQYAQgA',
|
||||
'JhYhBLgcq0yYY4G97CsGveVSFit0ImJRBQJRR1PRAhsMBQkDwmcAAAoJEOVSFit0',
|
||||
'ImJRWyUH/3QADLQRZ+3zuDCZU+qw+Sf1JWxJ/sTo2rVjhb0Vz3ixZe9u5nmxgCkI',
|
||||
'niPYNpBjkiGbLHAyQjsjp3l7qvUspnRMQD6eZALmcF/QBJYKUYAGjG7sC3vrIJoe',
|
||||
'S+ASw36sj/DMDTFYwTzIaAD+yXezCju8Ocbnfnjud/lcjoeDTlyxxLZAIeD6lY2s',
|
||||
'lE4/ChvdU5Cc+vANIANAy9wA9JjAdFzUfqhRpDGT8kmbMhDHlm8gM9gSg85mwImL',
|
||||
'2x0w/7zZoqzXzcemxs6XqOI3lWEGxnAZRA0e6A/WRfuPRhiclXPo7KaAmwRRlSwB',
|
||||
'fU3zrKk4kb6+V2qMReOORHr8jEYE0XG4jQRZu/43AQQA2gIZwFCK3ifxTTPOeyOd',
|
||||
'Z4ATBOBdzcDdTScr2sKApGtY4wVkEN8CDbply8kPJnWWyN03HbwyU7QCf/aRXYYd',
|
||||
'3/L/yJ1Ak0e2KU1hMrmlg3oeUGT9VC9FG4HwIozM+NakUj7qV89z+DrlyqPic9B7',
|
||||
'7vl3oSKuedC9MFj9pRgeqCcAEQEAAYkB6wQYAQgAIBYhBLgcq0yYY4G97CsGveVS',
|
||||
'Fit0ImJRBQJZu/43AhsCAL8JEOVSFit0ImJRtCAEGQEIAB0WIQRJU4+1+GHXjpXM',
|
||||
'hw+ODbvAFr5dIAUCWbv+NwAKCRCODbvAFr5dIIINA/4o6nx2V2ANYLIC3dvw+L1o',
|
||||
'hoQHvI5TJAln8UkG4AG9z5UzFJ7osnSgqvUXhLPFC+ifuWJFgIdAELUF9JS5rxSU',
|
||||
'HS0w61OgIjNnoS3JG9LFIjJ0clUMdqSz13Hxq7zJjgyjmlhFlZwvDTATHPEEqUQy',
|
||||
'FDSsvFvnhpL7uy32ffsoFfprB/0WGdJ9qo8UJBLlQVIn8fym+XNGuqz664MEQGmb',
|
||||
'+BDIdVBcSaOkD4pz+8GkyKEBD6J7aJRPmgJoVzc8/hgas+jpaaH0UhSwXGaRngRh',
|
||||
'PsWl9+qz7Tmb0OVdSGKpr7Bke6pjs31Ss1j2sL3XnYQKvfImuiCX4xaHBfXES/lR',
|
||||
'tzZzT8wyv2QQ1E4FRzR8S0d49Rcp8WI6rr4csbqkSRbw6qDcwyg7aEfn8Z88WNCK',
|
||||
'98zmzwSO6pxDWmVdPXgGjVa4MxRhAc16EBRFT/SH73D797Xnc0o1FJas5Du8AdEp',
|
||||
'qrZe+kBr7apBLKlv9rvHsc7OzTs39c8+rF1x4aF4Ys4ffaGcuI0EWbwAOQEEANKP',
|
||||
'LR+Ef/eYL5afw+ikIqlzRDEWHByhlWPaBquJ1xlaxPYhiwK5Ux8yZfnrYhlEKfhb',
|
||||
'O5Ieohh4HzuiOc0GToRqWQYSSqzJm5odBE2HAxkAGqGqLvW+9tcH5hPK3h/WRSWy',
|
||||
'QUwGvTqUfiGvg90CrIJ73DYPgy02iDlYtf+fVNgFABEBAAGJATYEGAEIACAWIQS4',
|
||||
'HKtMmGOBvewrBr3lUhYrdCJiUQUCWbwAOQIbDAAKCRDlUhYrdCJiUaS0B/sHG4+d',
|
||||
'r/6LuLIbsjvyIg+gVFK9sMGA751uZAZtdkzilw0c9cl0SGlxrhnRZwjZXlfB7RLD',
|
||||
'GLWuTzxX6vKVXjJ+IEY49QQ53ZLwcvUvpDlWhiJXRJHSiSIXestsnJp2bLVbfRbi',
|
||||
'V4jPXvZrgtBjU1XfpF9Ln6KqJGIwdWuKiHKm/iAr1qWZEtgj9AdtHWjlXOf+WWno',
|
||||
'tEfaA+aJefWpDs0YVQ/7LP1N70pHj2YwmQ/Cdyr1qo6iE66UMTkiGluPpGzR5yu9',
|
||||
'lhwmDcbAPbER1lIeU1K1TTWU92XCUaVuA/fPMKISlmb7UBDKWAE7904iOYOg4xsk',
|
||||
'4z6aN5G1sT5159GF',
|
||||
'=yzZh',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||
|
||||
it('Parsing armored text with RSA key and ECC subkey in tolerant mode', function(done) {
|
||||
openpgp.config.tolerant = true;
|
||||
var pubKeys = openpgp.key.readArmored(rsa_ecc_pub);
|
||||
expect(pubKeys).to.exist;
|
||||
expect(pubKeys.err).to.not.exist;
|
||||
@ -536,6 +592,66 @@ var pgp_desktop_priv =
|
||||
done();
|
||||
});
|
||||
|
||||
it('Parsing armored text with RSA key and ECC subkey in non-tolerant mode', function(done) {
|
||||
openpgp.config.tolerant = false;
|
||||
var pubKeys = openpgp.key.readArmored(rsa_ecc_pub);
|
||||
expect(pubKeys).to.exist;
|
||||
expect(pubKeys.err).to.exist;
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
var multi_uid_key =
|
||||
['-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||
'Version: GnuPG v1',
|
||||
'',
|
||||
'mQENBFbqatUBCADmeA9CjMfzLt3TrplzDxroVisCWO7GRErUXiozULZd5S8p/rHS',
|
||||
'kuclUsQzraSuQ+Q7RhpOWdJt9onf5ro0dCC3i+AEWBrS0nyXGAtpgxJmZ618Cwzz',
|
||||
'RKrYstce4Hsyg0NS1KCbzCxpfIbyU/GOx4AzsvP3BcbRMvJ6fvrKy6zrhyVq5to3',
|
||||
'c6MayKm3cTW0+iDvqbQCMXeKH1MgAj1eOBNrbgQZhTBMhAaIFUb5l9lXUXUmZmSj',
|
||||
'r4pjjVZjWudFswXPoVRGpCOU+ahJFZLeIca99bHOl3Hu+fEbVExHdoaVq5W9R/QJ',
|
||||
'/0bHQrd+Th8e1qpIP2/ABb6P/7SGUKw6ZUvbABEBAAG0E1Rlc3QgVXNlciA8YUBi',
|
||||
'LmNvbT6JATgEEwECACIFAlbqatUCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA',
|
||||
'AAoJEPhuIdU05lVRgtoH/ioJdP34cHIdSu2Ofsm6FoWc/nk2QEughNn2AyaxZAKO',
|
||||
'pWy9o9/+KlVD3SoV5fzl6tCsFz1MqLFBsHSj2wKoQqkU6S9MnrG12HgnirqcjOa0',
|
||||
'1uPB0aAqF3ptNScPqcD44bZ4p58TAeU5H7UlrwPUn4gypotAnu+zocNaqe0tKWVo',
|
||||
'f+GAZG/FuXJc5OK2J6OmKIABJCuRchXbkyfsXZYE3f+1U9mLse4wHQhGRiSlgqG4',
|
||||
'CCSIjeIkqeIvLCj/qGXJGyJ0XeMwMVhajylhEtDmMRlc32Jt8btlTJzcQ/3NPuQd',
|
||||
'EryD92vGp/fXwP1/rLtD49o/0UbDeXT4KQphs2DuG/60E1Rlc3QgVXNlciA8YkBj',
|
||||
'LmNvbT6JATgEEwECACIFAlbqeUACGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA',
|
||||
'AAoJEPhuIdU05lVRuPkIAK+ieYXEflVHY1bKeptYZ+UfHJhsBdM29WYmuHhAbWe9',
|
||||
'mb741n8YXbPENoCSYD4jq7cYOvrduz5QLmXKL57D9rXvu/dWhpLaSjGf4LDrSf+9',
|
||||
'bYw0U2BStjPzjnyxZSQDU60KFRIjZPWxF/VqRFp3QIp/r3vjEGuiE6JdzbT4EWwO',
|
||||
'rltkMzPYgx7cx63EhjrM3kybylL+wBX3T2JNCzLPfZBsdiWmQcypLgOPLrW/4fxQ',
|
||||
'zfAsDyEYlRj7xhVKAc+nMcXo8Hw46AecS8N3htZHM6WeekZYdoJ4DlDeE5RL76xZ',
|
||||
'hVEOziY5UnBT/F8dfZoVcyY/5FiSUuL19Cpwoc+dpWm5AQ0EVupq1QEIAMLfhMdk',
|
||||
'OoIl1J3J8F89My2u7qwKrw1WLWawBacZH2jsGZrjZlUJEIQpaIyvqHSPSgLJ+Yco',
|
||||
'YmCMj/ElNVBKBzaUpfdftW+5/S5OaJVq/j7J1OKMQqXQALgwh8GM/AThO5G4B27c',
|
||||
'HZ/+bkbldYJJK0y5ZONEj7gkch7w6cr1+6NCL7jMWIDar3HpchddOproxAMuZa9D',
|
||||
'2RjOvl+OMb6JMO5zTFbh37o5fAw3YWbmeX/tp2bD5W4lSUGD/Xwf2zS2r7vwGVZO',
|
||||
'C+zx1aaSNllcRvSWkg8zRY5FjL9AOl4l52JFfz8G63EuHrR9dXmsYA9IHunk0UNy',
|
||||
'/GGCcIJ6rXKTMCUAEQEAAYkBHwQYAQIACQUCVupq1QIbDAAKCRD4biHVNOZVUUFY',
|
||||
'CADkAAtvIiJLoiYyWBx4qdTuHecuBC8On64Ln2PqImowpMb8r5JzMP6aAIBxgfEt',
|
||||
'LezjJQbIM6Tcr6nTr1FunbAznrji1s4T6YcrRCS2QLq2j1aDUnLBFPrlAbuRnmZj',
|
||||
'o8miZXTSasZw4O8R56jmsbcebivekg0JQMiEsf3TfxmeFQrjSGKGBarn0aklfwDS',
|
||||
'JuhA5hs46N+HGvngXVZNAM9grFNxusp2YhC+DVDtcvR3SCVnVRfQojyaUKDEofHw',
|
||||
'YD+tjFrH9uxzUEF+0p6he6DJ5KrQuy5Zq4Yc4X2rNvtjsIzww0Byymvo6eRO0Gxk',
|
||||
'ljIYQms3pCv1ja6bLlNKpPII',
|
||||
'=qxBI',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||
|
||||
var wrong_key =
|
||||
['-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||
'Version: OpenPGP.js v0.9.0',
|
||||
'',
|
||||
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5',
|
||||
'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg',
|
||||
'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM',
|
||||
'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq',
|
||||
'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1',
|
||||
'=6XMW',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||
|
||||
it('Parsing armored text with two keys', function(done) {
|
||||
var pubKeys = openpgp.key.readArmored(twoKeys);
|
||||
expect(pubKeys).to.exist;
|
||||
@ -737,11 +853,11 @@ var pgp_desktop_priv =
|
||||
it('update() - merge subkey binding signatures', function() {
|
||||
var source = openpgp.key.readArmored(pgp_desktop_pub).keys[0];
|
||||
var dest = openpgp.key.readArmored(pgp_desktop_priv).keys[0];
|
||||
expect(source.subKeys[0].bindingSignature).to.exist;
|
||||
expect(source.subKeys[0].bindingSignatures[0]).to.exist;
|
||||
expect(source.subKeys[0].verify(source.primaryKey)).to.equal(openpgp.enums.keyStatus.valid);
|
||||
expect(dest.subKeys[0].bindingSignature).to.not.exist;
|
||||
expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist;
|
||||
dest.update(source);
|
||||
expect(dest.subKeys[0].bindingSignature).to.exist;
|
||||
expect(dest.subKeys[0].bindingSignatures[0]).to.exist;
|
||||
expect(dest.subKeys[0].verify(source.primaryKey)).to.equal(openpgp.enums.keyStatus.valid);
|
||||
});
|
||||
|
||||
@ -775,12 +891,12 @@ var pgp_desktop_priv =
|
||||
var keyFlags = openpgp.enums.keyFlags;
|
||||
expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.certify_keys).to.equal(keyFlags.certify_keys);
|
||||
expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.sign_data).to.equal(keyFlags.sign_data);
|
||||
expect(key.subKeys[0].bindingSignature.keyFlags[0] & keyFlags.encrypt_communication).to.equal(keyFlags.encrypt_communication);
|
||||
expect(key.subKeys[0].bindingSignature.keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage);
|
||||
expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_communication).to.equal(keyFlags.encrypt_communication);
|
||||
expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage);
|
||||
var sym = openpgp.enums.symmetric;
|
||||
expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes128, sym.aes192, sym.cast5, sym.tripledes]);
|
||||
var hash = openpgp.enums.hash;
|
||||
expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha1, hash.sha512]);
|
||||
expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha512, hash.sha1]);
|
||||
var compr = openpgp.enums.compression;
|
||||
expect(key.users[0].selfCertifications[0].preferredCompressionAlgorithms).to.eql([compr.zlib, compr.zip]);
|
||||
expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.integrity_protect ? [1] : null); // modification detection
|
||||
@ -873,6 +989,172 @@ var pgp_desktop_priv =
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('Generate key - ensure keyExpirationTime works', function(done) {
|
||||
var expect_delta = 365 * 24 * 60 * 60;
|
||||
var userId = 'test <a@b.com>';
|
||||
var opt = {numBits: 512, userIds: userId, passphrase: '123', keyExpirationTime: expect_delta};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key;
|
||||
|
||||
var expiration = key.getExpirationTime();
|
||||
expect(expiration).to.exist;
|
||||
|
||||
var actual_delta = (new Date(expiration) - new Date()) / 1000;
|
||||
expect(Math.abs(actual_delta - expect_delta)).to.be.below(60);
|
||||
|
||||
var subKeyExpiration = key.subKeys[0].getExpirationTime();
|
||||
expect(subKeyExpiration).to.exist;
|
||||
|
||||
var actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000;
|
||||
expect(Math.abs(actual_subKeyDelta - expect_delta)).to.be.below(60);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Sign and verify key - primary user', function(done) {
|
||||
var key = openpgp.key.readArmored(pub_sig_test).keys[0];
|
||||
var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||
privateKey.decrypt('hello world');
|
||||
key = key.signPrimaryUser([privateKey]);
|
||||
var signatures = key.verifyPrimaryUser([privateKey]);
|
||||
expect(signatures.length).to.equal(2);
|
||||
expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[0].valid).to.be.null;
|
||||
expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[1].valid).to.be.true;
|
||||
done();
|
||||
});
|
||||
|
||||
it('Sign key and verify with wrong key - primary user', function(done) {
|
||||
var key = openpgp.key.readArmored(pub_sig_test).keys[0];
|
||||
var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||
var wrongKey = openpgp.key.readArmored(wrong_key).keys[0];
|
||||
privateKey.decrypt('hello world');
|
||||
key = key.signPrimaryUser([privateKey]);
|
||||
var signatures = key.verifyPrimaryUser([wrongKey]);
|
||||
expect(signatures.length).to.equal(2);
|
||||
expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[0].valid).to.be.null;
|
||||
expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[1].valid).to.be.null;
|
||||
done();
|
||||
});
|
||||
|
||||
it('Sign and verify key - all users', function(done) {
|
||||
var key = openpgp.key.readArmored(multi_uid_key).keys[0];
|
||||
var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||
privateKey.decrypt('hello world');
|
||||
key = key.signAllUsers([privateKey]);
|
||||
var signatures = key.verifyAllUsers([privateKey]);
|
||||
expect(signatures.length).to.equal(4);
|
||||
expect(signatures[0].userid).to.equal(key.users[0].userId.userid);
|
||||
expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[0].valid).to.be.null;
|
||||
expect(signatures[1].userid).to.equal(key.users[0].userId.userid);
|
||||
expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[1].valid).to.be.true;
|
||||
expect(signatures[2].userid).to.equal(key.users[1].userId.userid);
|
||||
expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[2].valid).to.be.null;
|
||||
expect(signatures[3].userid).to.equal(key.users[1].userId.userid);
|
||||
expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[3].valid).to.be.true;
|
||||
done();
|
||||
});
|
||||
|
||||
it('Sign key and verify with wrong key - all users', function(done) {
|
||||
var key = openpgp.key.readArmored(multi_uid_key).keys[0];
|
||||
var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||
var wrongKey = openpgp.key.readArmored(wrong_key).keys[0];
|
||||
privateKey.decrypt('hello world');
|
||||
key = key.signAllUsers([privateKey]);
|
||||
var signatures = key.verifyAllUsers([wrongKey]);
|
||||
expect(signatures.length).to.equal(4);
|
||||
expect(signatures[0].userid).to.equal(key.users[0].userId.userid);
|
||||
expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[0].valid).to.be.null;
|
||||
expect(signatures[1].userid).to.equal(key.users[0].userId.userid);
|
||||
expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[1].valid).to.be.null;
|
||||
expect(signatures[2].userid).to.equal(key.users[1].userId.userid);
|
||||
expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[2].valid).to.be.null;
|
||||
expect(signatures[3].userid).to.equal(key.users[1].userId.userid);
|
||||
expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(signatures[3].valid).to.be.null;
|
||||
done();
|
||||
});
|
||||
it('Reformat key without passphrase', function(done) {
|
||||
var userId1 = 'test1 <a@b.com>';
|
||||
var userId2 = 'test2 <b@a.com>';
|
||||
var opt = {numBits: 512, userIds: userId1};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key
|
||||
expect(key.users.length).to.equal(1);
|
||||
expect(key.users[0].userId.userid).to.equal(userId1);
|
||||
expect(key.primaryKey.isDecrypted).to.be.true;
|
||||
opt.privateKey = key;
|
||||
opt.userIds = userId2;
|
||||
openpgp.reformatKey(opt).then(function(newKey) {
|
||||
newKey = newKey.key
|
||||
expect(newKey.users.length).to.equal(1);
|
||||
expect(newKey.users[0].userId.userid).to.equal(userId2);
|
||||
expect(newKey.primaryKey.isDecrypted).to.be.true;
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
it('Reformat and encrypt key', function(done) {
|
||||
var userId1 = 'test1 <a@b.com>';
|
||||
var userId2 = 'test2 <b@c.com>';
|
||||
var userId3 = 'test3 <c@d.com>';
|
||||
var opt = {numBits: 512, userIds: userId1};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key
|
||||
opt.privateKey = key;
|
||||
opt.userIds = [userId2, userId3];
|
||||
opt.passphrase = '123';
|
||||
openpgp.reformatKey(opt).then(function(newKey) {
|
||||
newKey = newKey.key
|
||||
expect(newKey.users.length).to.equal(2);
|
||||
expect(newKey.users[0].userId.userid).to.equal(userId2);
|
||||
expect(newKey.primaryKey.isDecrypted).to.be.false;
|
||||
newKey.decrypt('123');
|
||||
expect(newKey.primaryKey.isDecrypted).to.be.true;
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
it('Sign and encrypt with reformatted key', function(done) {
|
||||
var userId1 = 'test1 <a@b.com>';
|
||||
var userId2 = 'test2 <b@a.com>';
|
||||
var opt = {numBits: 512, userIds: userId1};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key
|
||||
opt.privateKey = key;
|
||||
opt.userIds = userId2;
|
||||
openpgp.reformatKey(opt).then(function(newKey) {
|
||||
newKey = newKey.key
|
||||
openpgp.encrypt({data: 'hello', publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(function(encrypted) {
|
||||
openpgp.decrypt({message: openpgp.message.readArmored(encrypted.data), privateKey: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal('hello');
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Find a valid subkey binding signature among many invalid ones', function(done) {
|
||||
var k = openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub).keys[0];
|
||||
expect(k.getEncryptionKeyPacket()).to.not.be.null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -289,7 +289,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||
var opt = {
|
||||
userIds: { name: 'Test User' }
|
||||
};
|
||||
openpgp.generateKey(opt).then(function() { done(); });
|
||||
openpgp.generateKey(opt).then(function() { done(); })
|
||||
});
|
||||
|
||||
it('should have default params set', function(done) {
|
||||
@ -303,13 +303,14 @@ describe('OpenPGP.js public api tests', function() {
|
||||
userIds: ['Test User <text@example.com>'],
|
||||
passphrase: 'secret',
|
||||
numBits: 2048,
|
||||
unlocked: true
|
||||
unlocked: true,
|
||||
keyExpirationTime: 0
|
||||
}).calledOnce).to.be.true;
|
||||
expect(newKey.key).to.exist;
|
||||
expect(newKey.privateKeyArmored).to.exist;
|
||||
expect(newKey.publicKeyArmored).to.exist;
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('should work for no params', function(done) {
|
||||
@ -318,7 +319,8 @@ describe('OpenPGP.js public api tests', function() {
|
||||
userIds: [],
|
||||
passphrase: undefined,
|
||||
numBits: 2048,
|
||||
unlocked: false
|
||||
unlocked: false,
|
||||
keyExpirationTime: 0
|
||||
}).calledOnce).to.be.true;
|
||||
expect(newKey.key).to.exist;
|
||||
done();
|
||||
@ -635,7 +637,8 @@ describe('OpenPGP.js public api tests', function() {
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures).to.not.exist;
|
||||
expect(decrypted.signatures).to.exist;
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -657,6 +660,172 @@ describe('OpenPGP.js public api tests', function() {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should encrypt/sign and decrypt/verify with detached signatures', function(done) {
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
openpgp.encrypt(encOpt).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
decOpt.signature = openpgp.signature.readArmored(encrypted.signature);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should encrypt and decrypt/verify with detached signature input and detached flag set for encryption', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys[0],
|
||||
detached: true
|
||||
};
|
||||
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
detached: true
|
||||
};
|
||||
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: publicKey.keys[0]
|
||||
};
|
||||
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
encOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.encrypt(encOpt);
|
||||
}).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
decOpt.signature = openpgp.signature.readArmored(encrypted.signature);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption', function(done) {
|
||||
var privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0];
|
||||
privKeyDE.decrypt(passphrase);
|
||||
|
||||
var pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0];
|
||||
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privKeyDE,
|
||||
detached: true
|
||||
};
|
||||
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
privateKeys: privateKey.keys[0]
|
||||
};
|
||||
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: [publicKey.keys[0], pubKeyDE]
|
||||
};
|
||||
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
encOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.encrypt(encOpt);
|
||||
}).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
expect(decrypted.signatures[1].valid).to.be.true;
|
||||
expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[1].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to encrypt and decrypt/verify with detached signature input and detached flag set for encryption with wrong public key', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
detached: true
|
||||
};
|
||||
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: openpgp.key.readArmored(wrong_pubkey).keys
|
||||
};
|
||||
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
encOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.encrypt(encOpt);
|
||||
}).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
decOpt.signature = openpgp.signature.readArmored(encrypted.signature);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption with wrong public key', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: openpgp.key.readArmored(wrong_pubkey).keys
|
||||
};
|
||||
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
encOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.encrypt(encOpt);
|
||||
}).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -678,6 +847,52 @@ describe('OpenPGP.js public api tests', function() {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully decrypt signed message without public keys to verify', function(done) {
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
privateKeys: privateKey.keys
|
||||
};
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
};
|
||||
openpgp.encrypt(encOpt).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to verify decrypted data with wrong public pgp key with detached signatures', function(done) {
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: openpgp.key.readArmored(wrong_pubkey).keys
|
||||
};
|
||||
openpgp.encrypt(encOpt).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
decOpt.signature = openpgp.signature.readArmored(encrypted.signature);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -698,6 +913,29 @@ describe('OpenPGP.js public api tests', function() {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and verify cleartext data with detached signatures', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var verifyOpt = {
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
verifyOpt.message = openpgp.cleartext.readArmored(signed.data);
|
||||
verifyOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.verify(verifyOpt);
|
||||
}).then(function(verified) {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -717,6 +955,29 @@ describe('OpenPGP.js public api tests', function() {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.null;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and fail to verify cleartext data with wrong public pgp key with detached signature', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var verifyOpt = {
|
||||
publicKeys: openpgp.key.readArmored(wrong_pubkey).keys
|
||||
};
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
verifyOpt.message = openpgp.cleartext.readArmored(signed.data);
|
||||
verifyOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.verify(verifyOpt);
|
||||
}).then(function(verified) {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.null;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -737,6 +998,30 @@ describe('OpenPGP.js public api tests', function() {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and verify cleartext data and not armor with detached signatures', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true,
|
||||
armor: false
|
||||
};
|
||||
var verifyOpt = {
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
verifyOpt.message = signed.message;
|
||||
verifyOpt.signature = signed.signature;
|
||||
return openpgp.verify(verifyOpt);
|
||||
}).then(function(verified) {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -760,6 +1045,9 @@ describe('OpenPGP.js public api tests', function() {
|
||||
}).then(function(encrypted) {
|
||||
expect(encrypted.data).to.exist;
|
||||
expect(encrypted.data).to.equal(plaintext);
|
||||
expect(encrypted.signatures[0].valid).to.be.true;
|
||||
expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(encrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -826,6 +1114,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||
|
||||
openpgp.decrypt({ privateKey:privKey, message:message }).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal('hello 3des\n');
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -845,6 +1134,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -862,6 +1152,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -880,6 +1171,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -903,6 +1195,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||
expect(encOpt.data.byteLength).to.equal(0); // transfered buffer should be empty
|
||||
}
|
||||
expect(decrypted.data).to.deep.equal(new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]));
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -265,6 +265,7 @@ describe("Signature", function() {
|
||||
openpgp.decrypt({ privateKey: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) {
|
||||
expect(decrypted.data).to.exist;
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -309,6 +310,7 @@ describe("Signature", function() {
|
||||
expect(verified).to.exist;
|
||||
expect(verified).to.have.length(1);
|
||||
expect(verified[0].valid).to.be.true;
|
||||
expect(verified[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -333,6 +335,7 @@ describe("Signature", function() {
|
||||
expect(verified).to.exist;
|
||||
expect(verified).to.have.length(1);
|
||||
expect(verified[0].valid).to.be.true;
|
||||
expect(verified[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -356,6 +359,7 @@ describe("Signature", function() {
|
||||
expect(verified).to.exist;
|
||||
expect(verified).to.have.length(1);
|
||||
expect(verified[0].valid).to.be.true;
|
||||
expect(verified[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -390,6 +394,7 @@ describe("Signature", function() {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures).to.have.length(1);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -426,6 +431,7 @@ describe("Signature", function() {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures).to.have.length(1);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -469,6 +475,8 @@ describe("Signature", function() {
|
||||
expect(verifiedSig).to.have.length(2);
|
||||
expect(verifiedSig[0].valid).to.be.true;
|
||||
expect(verifiedSig[1].valid).to.be.true;
|
||||
expect(verifiedSig[0].signature.packets.length).to.equal(1);
|
||||
expect(verifiedSig[1].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -513,11 +521,13 @@ describe("Signature", function() {
|
||||
expect(cleartextSig.signatures).to.have.length(2);
|
||||
expect(cleartextSig.signatures[0].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[1].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
|
||||
expect(cleartextSig.signatures[1].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Sign text with openpgp.sign and verify with openpgp.verify leads to same cleartext and valid signatures', function(done) {
|
||||
it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures', function(done) {
|
||||
var plaintext = 'short message\nnext line\n한국어/조선말';
|
||||
var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
|
||||
var privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
|
||||
@ -533,6 +543,49 @@ describe("Signature", function() {
|
||||
expect(cleartextSig.data).to.equal(plaintext.replace(/\r/g,''));
|
||||
expect(cleartextSig.signatures).to.have.length(1);
|
||||
expect(cleartextSig.signatures[0].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - armored', function(done) {
|
||||
var plaintext = openpgp.util.str2Uint8Array('short message\nnext line\n한국어/조선말');
|
||||
var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
|
||||
var privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
|
||||
privKey.getSigningKeyPacket().decrypt('hello world');
|
||||
|
||||
openpgp.sign({ privateKeys:[privKey], data:plaintext }).then(function(signed) {
|
||||
var csMsg = openpgp.message.readArmored(signed.data);
|
||||
return openpgp.verify({ publicKeys:[pubKey], message:csMsg });
|
||||
|
||||
}).then(function(cleartextSig) {
|
||||
expect(cleartextSig).to.exist;
|
||||
expect(cleartextSig.data).to.deep.equal(plaintext);
|
||||
expect(cleartextSig.signatures).to.have.length(1);
|
||||
expect(cleartextSig.signatures[0].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - not armored', function(done) {
|
||||
var plaintext = openpgp.util.str2Uint8Array('short message\nnext line\n한국어/조선말');
|
||||
var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
|
||||
var privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
|
||||
privKey.getSigningKeyPacket().decrypt('hello world');
|
||||
|
||||
openpgp.sign({ privateKeys:[privKey], data:plaintext, armor:false }).then(function(signed) {
|
||||
var csMsg = signed.message;
|
||||
return openpgp.verify({ publicKeys:[pubKey], message:csMsg });
|
||||
|
||||
}).then(function(cleartextSig) {
|
||||
expect(cleartextSig).to.exist;
|
||||
expect(cleartextSig.data).to.deep.equal(plaintext);
|
||||
expect(cleartextSig.signatures).to.have.length(1);
|
||||
expect(cleartextSig.signatures[0].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -595,7 +648,7 @@ describe("Signature", function() {
|
||||
expect(pubKey.users[0].selfCertifications).to.eql(pubKey2.users[0].selfCertifications);
|
||||
});
|
||||
|
||||
it('Verify a detached signature', function() {
|
||||
it('Verify a detached signature using readSignedContent', function() {
|
||||
var detachedSig = ['-----BEGIN PGP SIGNATURE-----',
|
||||
'Version: GnuPG v1.4.13 (Darwin)',
|
||||
'Comment: GPGTools - https://gpgtools.org',
|
||||
@ -641,6 +694,23 @@ describe("Signature", function() {
|
||||
expect(result[0].valid).to.be.true;
|
||||
});
|
||||
|
||||
it('Detached signature signing and verification', function () {
|
||||
var msg = openpgp.message.fromText('hello');
|
||||
var pubKey2 = openpgp.key.readArmored(pub_key_arm2).keys[0];
|
||||
var privKey2 = openpgp.key.readArmored(priv_key_arm2).keys[0];
|
||||
privKey2.decrypt('hello world');
|
||||
|
||||
var opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(gen) {
|
||||
var generatedKey = gen.key;
|
||||
var detachedSig = msg.signDetached([generatedKey, privKey2]);
|
||||
var result = msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]);
|
||||
expect(result[0].valid).to.be.true;
|
||||
expect(result[1].valid).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('Sign message with key without password', function(done) {
|
||||
var opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
@ -655,4 +725,42 @@ describe("Signature", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Verify signed key', function(done) {
|
||||
var signedArmor = [
|
||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||
'Version: GnuPG v1',
|
||||
'',
|
||||
'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+',
|
||||
'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5',
|
||||
'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0',
|
||||
'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS',
|
||||
'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6',
|
||||
'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki',
|
||||
'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf',
|
||||
'9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rCIRgQQEQIABgUCVuXBfQAK',
|
||||
'CRARJ5QDyxae+O0fAJ9hUQPejXvZv6VW1Q3/Pm3+x2wfJACgwFg9NlrPPfejoC1w',
|
||||
'P+z+vE5NFA24jQRSYS9OAQQA6R/PtBFaJaT4jq10yqASk4sqwVMsc6HcifM5lSdx',
|
||||
'zExFP74naUMMyEsKHP53QxTF0GrqusagQg/ZtgT0CN1HUM152y7ACOdp1giKjpMz',
|
||||
'OTQClqCoclyvWOFB+L/SwGEIJf7LSCErwoBuJifJc8xAVr0XX0JthoW+uP91eTQ3',
|
||||
'XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIbLgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJS',
|
||||
'YS9OAAoJEOCE90RsICyXuqIEANmmiRCASF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3',
|
||||
'Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhPGLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0',
|
||||
'Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tu',
|
||||
'zPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0XW748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxq',
|
||||
'E9+QCEl7qinFqqBLjuzgUhBU4QlwX1GDAtNTq6ihLMD5v1d82ZC7tNatdlDMGWnI',
|
||||
'dvEMCv2GZcuIqDQ9rXWs49e7tq1NncLYhz3tYjKhoFTKEIq3y3Pp',
|
||||
'=fvK7',
|
||||
'-----END PGP PUBLIC KEY BLOCK-----'
|
||||
].join('\n');
|
||||
|
||||
var signedKey = openpgp.key.readArmored(signedArmor).keys[0];
|
||||
var signerKey = openpgp.key.readArmored(priv_key_arm1).keys[0];
|
||||
var signatures = signedKey.verifyPrimaryUser([signerKey]);
|
||||
expect(signatures[0].valid).to.be.null;
|
||||
expect(signatures[0].keyid.toHex()).to.equal(signedKey.primaryKey.getKeyId().toHex());
|
||||
expect(signatures[1].valid).to.be.true;
|
||||
expect(signatures[1].keyid.toHex()).to.equal(signerKey.primaryKey.getKeyId().toHex());
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -185,6 +185,12 @@ describe('Util unit tests', function() {
|
||||
var test = openpgp.util.decode_utf8.bind(null, {chameleon: true});
|
||||
expect(test).to.throw(Error, /Parameter "utf8" is not of type string/);
|
||||
});
|
||||
it('util.readNumber should not overflow until full range of uint32', function () {
|
||||
var ints = [Math.pow(2, 20), Math.pow(2, 25), Math.pow(2, 30), Math.pow(2, 32) - 1];
|
||||
for(var i = 0; i < ints.length; i++) {
|
||||
expect(openpgp.util.readNumber(openpgp.util.writeNumber(ints[i], 4))).to.equal(ints[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user