├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── SIGNED.md
├── lib
├── colgrep.js
├── err.js
├── gpg.js
├── index.js
├── keyring.js
├── main.js
└── parse.js
├── package-lock.json
├── package.json
├── src
├── colgrep.iced
├── err.iced
├── gpg.iced
├── index.iced
├── keyring.iced
├── main.iced
└── parse.iced
└── test
├── files
├── colgrep.iced
├── error.iced
├── gpg.iced
├── index.iced
├── keyring.iced
└── parse.iced
└── run.iced
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 | build-stamp
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.6 (2019-02-01)
2 |
3 | - Retire (new Buffer), use Buffer.from
4 |
5 | ## 1.0.5 (2015-05-27)
6 |
7 | Bugfixes:
8 | - Trim trailing/leading whitespace when verifying sig payloads
9 |
10 | ## 1.0.4 (2015-04-15)
11 |
12 | Bugfixes:
13 | - Disable use of options file for any one-shot verifications
14 |
15 | ## 1.0.3 (2015-02-06)
16 |
17 | Feature:
18 | - Lookup TTY and init pinentry
19 |
20 | ## 1.0.2 (2015-01-14)
21 |
22 | Bugfixes:
23 | - Complete the new KeyManager interface; implemented get_ekid()
24 | with a stub for now.
25 |
26 | ## 1.0.1 (2014-11-17)
27 |
28 | Bugfixes:
29 | - Parse revoked keys in output
30 | - Support for https://github.com/keybase/node-client/pull/179
31 |
32 | ## 1.0.0 (2014-09-23)
33 |
34 | Features:
35 |
36 | - Address keybase/keybase-isssues#1002 and look for gpg2 first.
37 | - Also bump to a release version, this software is pretty mature by now.
38 |
39 | ## 0.0.46 and 0.0.47 (2014-06-05)
40 |
41 | Features:
42 |
43 | - Version upgrades and ICS v1.7.1-c upgrade
44 |
45 | ## 0.0.45 (2014-05-15)
46 |
47 | Bugfixes:
48 |
49 | - Fix bug in Indexing, wasn't properly looking for `ssb`s
50 | Add new test case for the above. Note that we need to supply `--with-fingerprint`
51 | twice to get gpg to output fingerprints for subkeys
52 |
53 | ## 0.0.44 (2014-03-29)
54 |
55 | Bugfixes:
56 | - Simplify read_uids_from_key, and use the Index system
57 | - Upgrade to pgp-utils@v0.0.19 to get more lax parsing
58 |
59 | ## 0.0.43 (2014-03-28)
60 |
61 | Features:
62 |
63 | - index2 which also has the ability to index secret keys and use a query
64 |
65 | ## 0.0.42 (2014-03-20)
66 |
67 | Bugfixes:
68 |
69 | - Close #5: Write an empty trust DB.
70 |
71 | ## 0.0.41 (2014-03-17)
72 |
73 | Features:
74 |
75 | - We probably should not call this a feature, but introduce the
76 | "nuclear" option for dealing with fussy gpg.conf files. Just
77 | ignore it for temporary keyrings. Only on if you specify it.
78 |
79 | ## 0.0.40 (2014-03-15)
80 |
81 | Bugfixes:
82 |
83 | - Address #4. Fix Indexing for people who have `with-fingerprint` in their
84 | `gpg.conf` files.
85 | - Fix bugs with parsing columns from `gpg --with-colons` output. We were
86 | mangling dates and also key id 64s
87 |
88 | ## 0.0.39 (2014-03-09)
89 |
90 | Bugfixes:
91 |
92 | - Better support for users with `secret-keyring` off on an external device. In practice,
93 | this means that we have to touch the temporary `secring.gpg` before we can import to it,
94 | a constraint which isn't enforced if `secret-keyring` isn't specified in the `gpg.conf` file.
95 | See issue keybase/keybase-issues#227
96 |
97 | ## 0.0.38 (2014-03-09)
98 |
99 | Features:
100 |
101 | - Better support for non-standard GPG
102 |
103 | ## 0.0.37 (2014-03-03)
104 |
105 | Features:
106 |
107 | - Do not print secret keys to stderr in debug
108 | - New `QuarantinedKeyRing` type that corresponds to incoming public keys that are
109 | not yet kosher.
110 |
111 | ## 0.0.36 (2014-02-27)
112 |
113 | Bugfixes:
114 |
115 | - Export armored PGP data, we were forgetting to do so in a couple of cases.
116 | - Use the iced-spawn@0.0.5 workaround to closing stdin bug on node 0.10.x
117 |
118 | ## 0.0.35 (2014-02-26)
119 |
120 | Features:
121 |
122 | - When loading keys, store all UIDs, not just the first, in the in-memory argument
123 |
124 | ## 0.0.34 (2014-02-25)
125 |
126 | Bugfixes:
127 |
128 | - Upgrade to pgp-utils v0.0.15 to allow null emails
129 |
130 | ## 0.0.33 (2014-02-20)
131 |
132 | Features :
133 |
134 | - If quiet is on, and there's an error, we'll pass stderr back via the Error object.
135 |
136 | ## 0.0.32 (2014-02-18)
137 |
138 | Bugfixes:
139 |
140 | - More robust and secure file-touching mechanism for new Alt primary key dirs
141 |
142 | ## 0.0.31 (2014-02-18)
143 |
144 | Bugfixes:
145 |
146 | - Issues with Alt primary dirs on windows being created for the first time.
147 |
148 | ## 0.0.30 (2014-02-17)
149 |
150 | Bugfixes:
151 |
152 | - We dropped set_log a while ago, when we moved spawn functionality into iced-spawn. So add it back.
153 |
154 | ## 0.0.29 (2014-02-17)
155 |
156 | Bugfixes
157 |
158 | - More windows testing bugfixes
159 |
160 | ## 0.0.28 (2014-02-16)
161 |
162 | Features:
163 |
164 | - New indexing system; can read in the whole keychain with -k and then access the index in memory (close issue #3)
165 | - Small tweaks and features additions for new keybase-node-installer version
166 |
167 |
168 | ## 0.0.27 (2014-02-15)
169 |
170 | Bugfixes:
171 |
172 | - Upgrade to `iced-spawn` for all spawning work.
173 | - Fix bugs in windows
174 |
175 | ## 0.0.25 (2014-02-14)
176 |
177 | Bugfixes:
178 |
179 | - `verify_sig` now goes through `one_shot_verify` which should ease the dependence on our ability to parse the text output of GPG
180 |
181 | Features:
182 |
183 | - Inaugural CHANGELOG.md
184 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, keybase
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | * Neither the name of the {organization} nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | default: build
2 | all: build
3 |
4 | ICED=node_modules/.bin/iced
5 | BUILD_STAMP=build-stamp
6 | TEST_STAMP=test-stamp
7 |
8 | default: build
9 | all: build
10 |
11 | lib/%.js: src/%.iced
12 | $(ICED) -I browserify -c -o `dirname $@` $<
13 |
14 | $(BUILD_STAMP): \
15 | lib/err.js \
16 | lib/gpg.js \
17 | lib/keyring.js \
18 | lib/index.js \
19 | lib/main.js \
20 | lib/parse.js \
21 | lib/colgrep.js
22 | date > $@
23 |
24 | clean:
25 | find lib -type f -name *.js -exec rm {} \;
26 |
27 | build: $(BUILD_STAMP)
28 |
29 | setup:
30 | npm install -d
31 |
32 | test: $(BUILD_STAMP)
33 | $(ICED) test/run.iced
34 |
35 | .PHONY: test setup
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gpg-wrapper
2 | ===========
3 |
4 | A wrapper around the GPG CLI.
5 |
--------------------------------------------------------------------------------
/SIGNED.md:
--------------------------------------------------------------------------------
1 | ##### Signed by https://keybase.io/max
2 | ```
3 | -----BEGIN PGP SIGNATURE-----
4 | Comment: GPGTools - https://gpgtools.org
5 |
6 | iQEcBAABCgAGBQJVZcKsAAoJEJgKPw0B/gTf2h8IAKAw5yxL+Epdzva4N33htlyn
7 | qTrAeqmZx7F2k7yythwR6X6T9LOjZFbsDvtiyG0x5qde2JBNbHjlDFegSwuk0Bzn
8 | /JIvQJPBbjnuXzYzM0GGq2uQbL4h/fL+UiAs4pzz2bGS6DmZii1Uw9vQto7go42H
9 | 0pAn2NUd+pfvN2+rknQxeUZ/fkQ5qfq+abbp5PxqR9TswyU5JQOv4en1XD1QGIuG
10 | AJgaEfDCmPLmitRCFrDzptx2in0WhvKj7YMj3nkIllPCTEZviwUz3lYpFs5o3ZlH
11 | 5fR3ltHTviinrGobkvoLf0g0HMBfJs4bnUxDI8bVv2T3DlrEm6BcQWS7l68aEMg=
12 | =gDTa
13 | -----END PGP SIGNATURE-----
14 |
15 | ```
16 |
17 |
18 |
19 | ### Begin signed statement
20 |
21 | #### Expect
22 |
23 | ```
24 | size exec file contents
25 | ./
26 | 109 .gitignore ec278daeb8f83cac2579d262b92ee6d7d872c4d1544e881ba515d8bcc05361ab
27 | 3993 CHANGELOG.md e6c3ea260df842104b2b55228ceeaafe659a017634ec52de36eba6cd743a1a6e
28 | 1483 LICENSE 333be7050513d91d9e77ca9acb4a91261721f0050209636076ed58676bfc643d
29 | 502 Makefile 960fe8002c2c2866c0963c9b0ed138dcb2a4feed693a3c668875935902f9b486
30 | 55 README.md fac7947ca164bd97f854cec88bc0266773ec378f4fb79cb1554662a4fd4079f9
31 | lib/
32 | 1100 colgrep.js 8cca2968a077b03d45b761139276f24f32b2a6948ada5fe825fcfd804105cda2
33 | 408 err.js ac74e7dbc52d8da10a4544bbedb78619a5407cdcdbb3893e7584fc5ca41c8e0d
34 | 11059 gpg.js 3e71dfc434d1daee56dd8f0f7e9d93651206327aff65e826190d310eebd28fc5
35 | 11120 index.js 821e84c563ead9729196d12a80f3d990cd485fbe16df6de264fdc457fd781286
36 | 93114 keyring.js ccff6d5dafd67f73c6cb28a9989bd5d4ab01a5481586e90cd795ac3ac3cf9a87
37 | 387 main.js 92476f33f1ce68c8f74c993c3b3d9603b9f435f44a69ec3098e552b0c4d736b8
38 | 3985 parse.js 57abc69755fc4eea76600b17082827fc23574fd7ba5b2a414cba40b89f0150a2
39 | 732 package.json b3d97cfe04eadac769156c5c4f633fcc214351008dc5f2d5f054e9f0176d9e77
40 | src/
41 | 604 colgrep.iced a3c53c57e739b9af47f7b8cdb31c3aaf3f7416c978e7905ec42bee4966bc3920
42 | 351 err.iced db7ddbbfbe1f076ad895a83e22cac8e720f260768456dd6aa0c97e5faf7ae9e5
43 | 2942 gpg.iced 41ced8c0c6e399a62d90f8a464c58e463d9fbf4d99a5383dd67d17529d4bc70e
44 | 5740 index.iced 8d9d3029f836f5c608a75d010e557cc7cc7da8444b530191c9ca4282952dd8b2
45 | 28659 keyring.iced 0614a1cbdcefadc1989f021e5ddfe3421945f353c2fcbb95195d0580b7c981a0
46 | 225 main.iced d06200c91a7f18bf1ece9ed92123ecf2362cf4592318ca23639b08356ce877ab
47 | 1731 parse.iced f031af161d3e124ef77ed4ff2e679b84db797c2462d407be0975383c30400857
48 | test/
49 | files/
50 | 1066 colgrep.iced e055590058160122daaddaf0fe2784394981c3d599a86cfce892b31dc89e030f
51 | 360 error.iced d47058171d6c5a61c57829d9b9fa05f6c06153ce899d6a6ae61dc13c32b956ac
52 | 1800 gpg.iced d460e8db71481e1b99ace3e3c6d614a3c4a7b71dea3dd5a7aa08033bbc1c6496
53 | 10403 index.iced fb73ed7d38df999852f8b68e0fcf2c37615f24eaca8d83e2f9b353d18380f338
54 | 36230 keyring.iced 1198771e057f5ed60cb03847334e173c67ef5e5982878586138bf1054adab571
55 | 1458 parse.iced 6502b4cbe550249868f8c9f257b621898ba76acf1e20242ecad1b811e0829c3d
56 | 183 run.iced 822568debeae702ca4d1f3026896d78b2d426e960d77cb3c374da059ef09f9fd
57 | ```
58 |
59 | #### Ignore
60 |
61 | ```
62 | /SIGNED.md
63 | ```
64 |
65 | #### Presets
66 |
67 | ```
68 | git # ignore .git and anything as described by .gitignore files
69 | dropbox # ignore .dropbox-cache and other Dropbox-related files
70 | kb # ignore anything as described by .kbignore files
71 | ```
72 |
73 |
74 |
75 | ### End signed statement
76 |
77 |
78 |
79 | #### Notes
80 |
81 | With keybase you can sign any directory's contents, whether it's a git repo,
82 | source code distribution, or a personal documents folder. It aims to replace the drudgery of:
83 |
84 | 1. comparing a zipped file to a detached statement
85 | 2. downloading a public key
86 | 3. confirming it is in fact the author's by reviewing public statements they've made, using it
87 |
88 | All in one simple command:
89 |
90 | ```bash
91 | keybase dir verify
92 | ```
93 |
94 | There are lots of options, including assertions for automating your checks.
95 |
96 | For more info, check out https://keybase.io/docs/command_line/code_signing
--------------------------------------------------------------------------------
/lib/colgrep.js:
--------------------------------------------------------------------------------
1 | // Generated by IcedCoffeeScript 1.7.1-c
2 | (function() {
3 | var colgrep;
4 |
5 | exports.colgrep = colgrep = function(_arg) {
6 | var buffer, cols, found, indices, k, line, lines, max_index, out, patterns, separator, v, _i, _len;
7 | patterns = _arg.patterns, buffer = _arg.buffer, separator = _arg.separator;
8 | separator || (separator = /:/);
9 | lines = buffer.toString('utf8').split('\n');
10 | indices = (function() {
11 | var _results;
12 | _results = [];
13 | for (k in patterns) {
14 | v = patterns[k];
15 | _results.push(parseInt(k));
16 | }
17 | return _results;
18 | })();
19 | max_index = Math.max.apply(Math, indices);
20 | out = [];
21 | for (_i = 0, _len = lines.length; _i < _len; _i++) {
22 | line = lines[_i];
23 | if (!(((cols = line.split(separator)) != null) && (max_index < cols.length))) {
24 | continue;
25 | }
26 | found = true;
27 | for (k in patterns) {
28 | v = patterns[k];
29 | if (!cols[k].match(v)) {
30 | found = false;
31 | break;
32 | }
33 | }
34 | if (found) {
35 | out.push(cols);
36 | }
37 | }
38 | return out;
39 | };
40 |
41 | }).call(this);
42 |
--------------------------------------------------------------------------------
/lib/err.js:
--------------------------------------------------------------------------------
1 | // Generated by IcedCoffeeScript 1.7.1-c
2 | (function() {
3 | var ie;
4 |
5 | ie = require('iced-error');
6 |
7 | exports.E = ie.make_errors({
8 | GPG: "Command line error",
9 | PGP_ID_COLLISION: "PGP ID collision error",
10 | NOT_FOUND: "Key wasn't found",
11 | CMD: "Non-zero exit code",
12 | PARSE: "parse error",
13 | VERIFY: "Signature verification error",
14 | NO_FINGERPRINT: "No fingerprint found"
15 | });
16 |
17 | }).call(this);
18 |
--------------------------------------------------------------------------------
/lib/gpg.js:
--------------------------------------------------------------------------------
1 | // Generated by IcedCoffeeScript 1.7.1-c
2 | (function() {
3 | var E, GPG, colgrep, iced, ispawn, make_esc, parse, set_gpg_cmd, spotty, __iced_k, __iced_k_noop, _gpg_cmd, _log, _tty;
4 |
5 | iced = require('iced-runtime').iced;
6 | __iced_k = __iced_k_noop = function() {};
7 |
8 | colgrep = require('./colgrep').colgrep;
9 |
10 | E = require('./err').E;
11 |
12 | parse = require('pgp-utils').userid.parse;
13 |
14 | ispawn = require('iced-spawn');
15 |
16 | make_esc = require('iced-error').make_esc;
17 |
18 | spotty = require('spotty');
19 |
20 | _gpg_cmd = "gpg";
21 |
22 | exports.set_gpg_cmd = set_gpg_cmd = function(c) {
23 | return _gpg_cmd = c;
24 | };
25 |
26 | exports.get_gpg_cmd = function() {
27 | return _gpg_cmd;
28 | };
29 |
30 | _log = null;
31 |
32 | exports.set_log = function(l) {
33 | return _log = l;
34 | };
35 |
36 | exports.find_and_set_cmd = function(cmd, cb) {
37 | var cmds, err, v, ___iced_passed_deferral, __iced_deferrals, __iced_k;
38 | __iced_k = __iced_k_noop;
39 | ___iced_passed_deferral = iced.findDeferral(arguments);
40 | (function(_this) {
41 | return (function(__iced_k) {
42 | if (cmd != null) {
43 | (function(__iced_k) {
44 | __iced_deferrals = new iced.Deferrals(__iced_k, {
45 | parent: ___iced_passed_deferral,
46 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
47 | funcname: "find_and_set_cmd"
48 | });
49 | (new GPG({
50 | cmd: cmd
51 | })).test(__iced_deferrals.defer({
52 | assign_fn: (function() {
53 | return function() {
54 | err = arguments[0];
55 | return v = arguments[1];
56 | };
57 | })(),
58 | lineno: 22
59 | }));
60 | __iced_deferrals._fulfill();
61 | })(function() {
62 | return __iced_k(typeof err !== "undefined" && err !== null ? err = new Error("Could not access the supplied GPG command '" + cmd + "'") : void 0);
63 | });
64 | } else {
65 | cmds = ["gpg2", "gpg"];
66 | (function(__iced_k) {
67 | var _i, _len, _ref, _results, _while;
68 | _ref = cmds;
69 | _len = _ref.length;
70 | _i = 0;
71 | _results = [];
72 | _while = function(__iced_k) {
73 | var _break, _continue, _next;
74 | _break = function() {
75 | return __iced_k(_results);
76 | };
77 | _continue = function() {
78 | return iced.trampoline(function() {
79 | ++_i;
80 | return _while(__iced_k);
81 | });
82 | };
83 | _next = function(__iced_next_arg) {
84 | _results.push(__iced_next_arg);
85 | return _continue();
86 | };
87 | if (!(_i < _len)) {
88 | return _break();
89 | } else {
90 | cmd = _ref[_i];
91 | (function(__iced_k) {
92 | __iced_deferrals = new iced.Deferrals(__iced_k, {
93 | parent: ___iced_passed_deferral,
94 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
95 | funcname: "find_and_set_cmd"
96 | });
97 | (new GPG({
98 | cmd: cmd
99 | })).test(__iced_deferrals.defer({
100 | assign_fn: (function() {
101 | return function() {
102 | err = arguments[0];
103 | return v = arguments[1];
104 | };
105 | })(),
106 | lineno: 28
107 | }));
108 | __iced_deferrals._fulfill();
109 | })(function() {
110 | (function(__iced_k) {
111 | if (!err) {
112 | (function(__iced_k) {
113 | _break()
114 | })(__iced_k);
115 | } else {
116 | return __iced_k();
117 | }
118 | })(_next);
119 | });
120 | }
121 | };
122 | _while(__iced_k);
123 | })(function() {
124 | return __iced_k(err != null ? err = new Error("Could not find GPG command: tried 'gpg2' and 'gpg'") : void 0);
125 | });
126 | }
127 | });
128 | })(this)((function(_this) {
129 | return function() {
130 | if (typeof err === "undefined" || err === null) {
131 | set_gpg_cmd(cmd);
132 | }
133 | return cb(err, v, cmd);
134 | };
135 | })(this));
136 | };
137 |
138 | _tty = null;
139 |
140 | exports.pinentry_init = function(cb) {
141 | var err, tmp, ___iced_passed_deferral, __iced_deferrals, __iced_k;
142 | __iced_k = __iced_k_noop;
143 | ___iced_passed_deferral = iced.findDeferral(arguments);
144 | (function(_this) {
145 | return (function(__iced_k) {
146 | __iced_deferrals = new iced.Deferrals(__iced_k, {
147 | parent: ___iced_passed_deferral,
148 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
149 | funcname: "pinentry_init"
150 | });
151 | spotty.tty(__iced_deferrals.defer({
152 | assign_fn: (function() {
153 | return function() {
154 | err = arguments[0];
155 | return tmp = arguments[1];
156 | };
157 | })(),
158 | lineno: 40
159 | }));
160 | __iced_deferrals._fulfill();
161 | });
162 | })(this)((function(_this) {
163 | return function() {
164 | _tty = tmp;
165 | return cb(err, _tty);
166 | };
167 | })(this));
168 | };
169 |
170 | exports.GPG = GPG = (function() {
171 | function GPG(opts) {
172 | var c;
173 | this.CMD = (c = opts != null ? opts.cmd : void 0) != null ? c : _gpg_cmd;
174 | }
175 |
176 | GPG.prototype.mutate_args = function(args) {};
177 |
178 | GPG.prototype.test = function(cb) {
179 | var err, out, ___iced_passed_deferral, __iced_deferrals, __iced_k;
180 | __iced_k = __iced_k_noop;
181 | ___iced_passed_deferral = iced.findDeferral(arguments);
182 | (function(_this) {
183 | return (function(__iced_k) {
184 | __iced_deferrals = new iced.Deferrals(__iced_k, {
185 | parent: ___iced_passed_deferral,
186 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
187 | funcname: "GPG.test"
188 | });
189 | ispawn.run({
190 | name: _this.CMD,
191 | args: ["--version"],
192 | quiet: true
193 | }, __iced_deferrals.defer({
194 | assign_fn: (function() {
195 | return function() {
196 | err = arguments[0];
197 | return out = arguments[1];
198 | };
199 | })(),
200 | lineno: 60
201 | }));
202 | __iced_deferrals._fulfill();
203 | });
204 | })(this)((function(_this) {
205 | return function() {
206 | return cb(err, out);
207 | };
208 | })(this));
209 | };
210 |
211 | GPG.prototype.run = function(inargs, cb) {
212 | var env, err, out, stderr, ___iced_passed_deferral, __iced_deferrals, __iced_k;
213 | __iced_k = __iced_k_noop;
214 | ___iced_passed_deferral = iced.findDeferral(arguments);
215 | stderr = null;
216 | this.mutate_args(inargs);
217 | env = process.env;
218 | delete env.LANGUAGE;
219 | if (_tty != null) {
220 | env.GPG_TTY = _tty;
221 | }
222 | inargs.name = this.CMD;
223 | inargs.eklass = E.GpgError;
224 | inargs.opts = {
225 | env: env
226 | };
227 | if (_log != null) {
228 | inargs.log = _log;
229 | }
230 | if ((inargs.stderr == null) && inargs.quiet) {
231 | inargs.stderr = stderr = new ispawn.BufferOutStream();
232 | }
233 | if (inargs.no_options) {
234 | inargs.args = ["--no-options"].concat(inargs.args);
235 | }
236 | (function(_this) {
237 | return (function(__iced_k) {
238 | __iced_deferrals = new iced.Deferrals(__iced_k, {
239 | parent: ___iced_passed_deferral,
240 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
241 | funcname: "GPG.run"
242 | });
243 | ispawn.run(inargs, __iced_deferrals.defer({
244 | assign_fn: (function() {
245 | return function() {
246 | err = arguments[0];
247 | return out = arguments[1];
248 | };
249 | })(),
250 | lineno: 77
251 | }));
252 | __iced_deferrals._fulfill();
253 | });
254 | })(this)((function(_this) {
255 | return function() {
256 | if ((typeof err !== "undefined" && err !== null) && (stderr != null)) {
257 | err.stderr = stderr.data();
258 | }
259 | return cb(err, out);
260 | };
261 | })(this));
262 | };
263 |
264 | GPG.prototype.command_line = function(inargs) {
265 | var v;
266 | this.mutate_args(inargs);
267 | v = [this.CMD].concat(inargs.args);
268 | return v.join(" ");
269 | };
270 |
271 | GPG.prototype.assert_no_collision = function(id, cb) {
272 | var args, err, n, out, rows, ___iced_passed_deferral, __iced_deferrals, __iced_k;
273 | __iced_k = __iced_k_noop;
274 | ___iced_passed_deferral = iced.findDeferral(arguments);
275 | args = ["-k", "--with-colons", id];
276 | n = 0;
277 | (function(_this) {
278 | return (function(__iced_k) {
279 | __iced_deferrals = new iced.Deferrals(__iced_k, {
280 | parent: ___iced_passed_deferral,
281 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
282 | funcname: "GPG.assert_no_collision"
283 | });
284 | _this.run({
285 | args: args,
286 | quiet: true
287 | }, __iced_deferrals.defer({
288 | assign_fn: (function() {
289 | return function() {
290 | err = arguments[0];
291 | return out = arguments[1];
292 | };
293 | })(),
294 | lineno: 94
295 | }));
296 | __iced_deferrals._fulfill();
297 | });
298 | })(this)((function(_this) {
299 | return function() {
300 | if (typeof err !== "undefined" && err !== null) {
301 |
302 | } else {
303 | rows = colgrep({
304 | patterns: {
305 | 0: /^[sp]ub$/,
306 | 4: new RegExp("^.*" + id + "$", "i")
307 | },
308 | buffer: out,
309 | separator: /:/
310 | });
311 | if ((n = rows.length) > 1) {
312 | err = new E.PgpIdCollisionError("Found two keys for ID=" + short_id);
313 | }
314 | }
315 | return cb(err, n);
316 | };
317 | })(this));
318 | };
319 |
320 | GPG.prototype.assert_exactly_one = function(short_id, cb) {
321 | var err, n, ___iced_passed_deferral, __iced_deferrals, __iced_k;
322 | __iced_k = __iced_k_noop;
323 | ___iced_passed_deferral = iced.findDeferral(arguments);
324 | (function(_this) {
325 | return (function(__iced_k) {
326 | __iced_deferrals = new iced.Deferrals(__iced_k, {
327 | parent: ___iced_passed_deferral,
328 | filename: "/Users/max/src/keybase/gpg-wrapper/src/gpg.iced",
329 | funcname: "GPG.assert_exactly_one"
330 | });
331 | _this.assert_no_collision(short_id, __iced_deferrals.defer({
332 | assign_fn: (function() {
333 | return function() {
334 | err = arguments[0];
335 | return n = arguments[1];
336 | };
337 | })(),
338 | lineno: 112
339 | }));
340 | __iced_deferrals._fulfill();
341 | });
342 | })(this)((function(_this) {
343 | return function() {
344 | if (n !== 1) {
345 | err = new E.NotFoundError("Didn't find a key for " + short_id);
346 | }
347 | return cb(err);
348 | };
349 | })(this));
350 | };
351 |
352 | return GPG;
353 |
354 | })();
355 |
356 | }).call(this);
357 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | // Generated by IcedCoffeeScript 1.7.1-c
2 | (function() {
3 | var BaseKey, BucketDict, Element, Ignored, Index, Key, Line, Parser, Subkey, Warnings, list_fingerprints, parse, parse_int, pgpu, uniquify, util,
4 | __hasProp = {}.hasOwnProperty,
5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
6 |
7 | pgpu = require('pgp-utils').userid;
8 |
9 | Warnings = require('iced-utils').util.Warnings;
10 |
11 | util = require('util');
12 |
13 | BucketDict = (function() {
14 | function BucketDict() {
15 | this._d = {};
16 | }
17 |
18 | BucketDict.prototype.add = function(k, v) {
19 | var b;
20 | k = ("" + k).toLowerCase();
21 | if ((b = this._d[k]) == null) {
22 | this._d[k] = b = [];
23 | }
24 | return b.push(v);
25 | };
26 |
27 | BucketDict.prototype.get = function(k) {
28 | return this._d[("" + k).toLowerCase()] || [];
29 | };
30 |
31 | BucketDict.prototype.get_0_or_1 = function(k) {
32 | var err, l, n, obj;
33 | l = this.get(k);
34 | err = obj = null;
35 | if ((n = l.length) > 1) {
36 | err = new Error("wanted a unique lookup, but got " + n + " object for key " + k);
37 | } else {
38 | obj = n === 0 ? null : l[0];
39 | }
40 | return [err, obj];
41 | };
42 |
43 | return BucketDict;
44 |
45 | })();
46 |
47 | uniquify = function(v) {
48 | var e, h, k, _i, _len, _results;
49 | h = {};
50 | for (_i = 0, _len = v.length; _i < _len; _i++) {
51 | e = v[_i];
52 | h[e] = true;
53 | }
54 | _results = [];
55 | for (k in h) {
56 | _results.push(k);
57 | }
58 | return _results;
59 | };
60 |
61 | Index = (function() {
62 | function Index() {
63 | this._keys = [];
64 | this._lookup = {
65 | email: new BucketDict(),
66 | fingerprint: new BucketDict(),
67 | key_id_64: new BucketDict()
68 | };
69 | }
70 |
71 | Index.prototype.push_element = function(el) {
72 | var k;
73 | if ((k = el.to_key())) {
74 | return this.index_key(k);
75 | }
76 | };
77 |
78 | Index.prototype.index_key = function(k) {
79 | var e, i, _i, _j, _len, _len1, _ref, _ref1, _results;
80 | this._keys.push(k);
81 | this._lookup.fingerprint.add(k.fingerprint(), k);
82 | _ref = uniquify(k.emails());
83 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
84 | e = _ref[_i];
85 | this._lookup.email.add(e, k);
86 | }
87 | _ref1 = k.all_key_id_64s();
88 | _results = [];
89 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
90 | i = _ref1[_j];
91 | _results.push(this._lookup.key_id_64.add(i, k));
92 | }
93 | return _results;
94 | };
95 |
96 | Index.prototype.lookup = function() {
97 | return this._lookup;
98 | };
99 |
100 | Index.prototype.keys = function() {
101 | return this._keys;
102 | };
103 |
104 | Index.prototype.fingerprints = function() {
105 | var k, _i, _len, _ref, _results;
106 | _ref = this.keys();
107 | _results = [];
108 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
109 | k = _ref[_i];
110 | _results.push(k.fingerprint());
111 | }
112 | return _results;
113 | };
114 |
115 | return Index;
116 |
117 | })();
118 |
119 | Element = (function() {
120 | function Element() {
121 | this._err = null;
122 | }
123 |
124 | Element.prototype.err = function() {
125 | return this._err;
126 | };
127 |
128 | Element.prototype.is_ok = function() {
129 | return this._err == null;
130 | };
131 |
132 | Element.prototype.to_key = function() {
133 | return null;
134 | };
135 |
136 | return Element;
137 |
138 | })();
139 |
140 | parse_int = function(s) {
141 | if (s != null ? s.match(/^[0-9]+$/) : void 0) {
142 | return parseInt(s, 10);
143 | } else {
144 | return s;
145 | }
146 | };
147 |
148 | BaseKey = (function(_super) {
149 | __extends(BaseKey, _super);
150 |
151 | function BaseKey(line) {
152 | var e, v;
153 | BaseKey.__super__.constructor.call(this);
154 | if (line.v.length < 12) {
155 | this._err = new Error("Key is malformed; needs at least 12 fields");
156 | } else {
157 | v = (function() {
158 | var _i, _len, _ref, _results;
159 | _ref = line.v;
160 | _results = [];
161 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
162 | e = _ref[_i];
163 | _results.push(parse_int(e));
164 | }
165 | return _results;
166 | })();
167 | this._pub = v[0], this._trust = v[1], this._n_bits = v[2], this._type = v[3], this._key_id_64 = v[4], this._created = v[5], this._expires = v[6];
168 | }
169 | }
170 |
171 | BaseKey.prototype.err = function() {
172 | return this._err;
173 | };
174 |
175 | BaseKey.prototype.to_key = function() {
176 | return null;
177 | };
178 |
179 | BaseKey.prototype.key_id_64 = function() {
180 | return this._key_id_64;
181 | };
182 |
183 | BaseKey.prototype.fingerprint = function() {
184 | return this._fingerprint;
185 | };
186 |
187 | BaseKey.prototype.add_fingerprint = function(line) {
188 | return this._fingerprint = line.get(9);
189 | };
190 |
191 | BaseKey.prototype.is_revoked = function() {
192 | return this._trust === 'r';
193 | };
194 |
195 | BaseKey.prototype.to_dict = function(_arg) {
196 | var secret;
197 | secret = _arg.secret;
198 | return {
199 | fingerprint: this.fingerprint(),
200 | key_id_64: this.key_id_64(),
201 | secret: secret,
202 | is_revoked: this.is_revoked()
203 | };
204 | };
205 |
206 | return BaseKey;
207 |
208 | })(Element);
209 |
210 | Subkey = (function(_super) {
211 | __extends(Subkey, _super);
212 |
213 | function Subkey() {
214 | return Subkey.__super__.constructor.apply(this, arguments);
215 | }
216 |
217 | return Subkey;
218 |
219 | })(BaseKey);
220 |
221 | Key = (function(_super) {
222 | __extends(Key, _super);
223 |
224 | function Key(line) {
225 | Key.__super__.constructor.call(this, line);
226 | this._userids = [];
227 | this._subkeys = [];
228 | this._top = this;
229 | if (this.is_ok()) {
230 | this.add_uid(line);
231 | }
232 | }
233 |
234 | Key.prototype.emails = function() {
235 | var e, u, _i, _len, _ref, _results;
236 | _ref = this._userids;
237 | _results = [];
238 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
239 | u = _ref[_i];
240 | if ((e = u.email) != null) {
241 | _results.push(e);
242 | }
243 | }
244 | return _results;
245 | };
246 |
247 | Key.prototype.to_key = function() {
248 | return this;
249 | };
250 |
251 | Key.prototype.userids = function() {
252 | return this._userids;
253 | };
254 |
255 | Key.prototype.subkeys = function() {
256 | return this._subkeys;
257 | };
258 |
259 | Key.prototype.to_dict = function(d) {
260 | var r;
261 | r = Key.__super__.to_dict.call(this, d);
262 | r.uid = this.userids()[0];
263 | r.all_uids = this.userids;
264 | return r;
265 | };
266 |
267 | Key.prototype.all_keys = function() {
268 | return [this].concat(this._subkeys);
269 | };
270 |
271 | Key.prototype.all_key_id_64s = function() {
272 | var i, ret, s;
273 | ret = (function() {
274 | var _i, _len, _ref, _results;
275 | _ref = this.all_keys();
276 | _results = [];
277 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
278 | s = _ref[_i];
279 | if ((i = s.key_id_64()) != null) {
280 | _results.push(i);
281 | }
282 | }
283 | return _results;
284 | }).call(this);
285 | return ret;
286 | };
287 |
288 | Key.prototype.add_line = function(line) {
289 | var err, f, n;
290 | err = null;
291 | if ((n = line.v.length) < 2) {
292 | return line.warn("got too few fields (" + n + ")");
293 | } else {
294 | switch ((f = line.v[0])) {
295 | case 'fpr':
296 | return this._top.add_fingerprint(line);
297 | case 'uid':
298 | return this.add_uid(line);
299 | case 'uat':
300 | break;
301 | case 'sub':
302 | case 'ssb':
303 | return this.add_subkey(line);
304 | default:
305 | return line.warn("unexpected subfield: " + f);
306 | }
307 | }
308 | };
309 |
310 | Key.prototype.add_subkey = function(line) {
311 | var key;
312 | key = new Subkey(line);
313 | if (key.is_ok()) {
314 | this._subkeys.push(key);
315 | return this._top = key;
316 | } else {
317 | return line.warn("Bad subkey: " + (key.err().message));
318 | }
319 | };
320 |
321 | Key.prototype.add_uid = function(line) {
322 | var e, u;
323 | if (((e = line.get(9)) != null) && ((u = pgpu.parse(e)) != null)) {
324 | return this._userids.push(u);
325 | }
326 | };
327 |
328 | return Key;
329 |
330 | })(BaseKey);
331 |
332 | Ignored = (function(_super) {
333 | __extends(Ignored, _super);
334 |
335 | function Ignored(line) {}
336 |
337 | return Ignored;
338 |
339 | })(Element);
340 |
341 | Line = (function() {
342 | function Line(txt, number, parser) {
343 | this.number = number;
344 | this.parser = parser;
345 | this.v = txt.split(":");
346 | if (this.v.length < 2) {
347 | this.warn("Bad line; expectect at least 2 fields");
348 | }
349 | }
350 |
351 | Line.prototype.warn = function(m) {
352 | return this.parser.warn(this.number + ": " + m);
353 | };
354 |
355 | Line.prototype.get = function(n) {
356 | if (n < this.v.length && this.v[n].length) {
357 | return this.v[n];
358 | } else {
359 | return null;
360 | }
361 | };
362 |
363 | return Line;
364 |
365 | })();
366 |
367 | exports.Parser = Parser = (function() {
368 | function Parser(txt) {
369 | this.txt = txt;
370 | this._warnings = new Warnings();
371 | this.init();
372 | }
373 |
374 | Parser.prototype.warn = function(w) {
375 | return this._warnings.push(w);
376 | };
377 |
378 | Parser.prototype.warnings = function() {
379 | return this._warnings;
380 | };
381 |
382 | Parser.prototype.init = function() {
383 | var i, l;
384 | return this.lines = (function() {
385 | var _i, _len, _ref, _results;
386 | _ref = this.txt.split(/\r?\n/);
387 | _results = [];
388 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
389 | l = _ref[i];
390 | if (l.length > 0) {
391 | _results.push(new Line(l, i + 1, this));
392 | }
393 | }
394 | return _results;
395 | }).call(this);
396 | };
397 |
398 | Parser.prototype.peek = function() {
399 | if (this.is_eof()) {
400 | return null;
401 | } else {
402 | return this.lines[0];
403 | }
404 | };
405 |
406 | Parser.prototype.get = function() {
407 | if (this.is_eof()) {
408 | return null;
409 | } else {
410 | return this.lines.shift();
411 | }
412 | };
413 |
414 | Parser.prototype.is_eof = function() {
415 | return this.lines.length === 0;
416 | };
417 |
418 | Parser.prototype.parse_ignored = function(line) {
419 | return new Ignored(line);
420 | };
421 |
422 | Parser.prototype.parse = function() {
423 | var element, index;
424 | index = new Index();
425 | while (!this.is_eof()) {
426 | if ((element = this.parse_element()) && element.is_ok()) {
427 | index.push_element(element);
428 | }
429 | }
430 | return index;
431 | };
432 |
433 | Parser.prototype.is_new_key = function(line) {
434 | var _ref;
435 | return (line != null) && ((_ref = line.get(0)) === 'pub' || _ref === 'sec');
436 | };
437 |
438 | Parser.prototype.parse_element = function() {
439 | var line;
440 | line = this.get();
441 | if (this.is_new_key(line)) {
442 | return this.parse_key(line);
443 | } else {
444 | return this.parse_ignored(line);
445 | }
446 | };
447 |
448 | Parser.prototype.parse_key = function(first_line) {
449 | var key, nxt;
450 | key = new Key(first_line);
451 | while (((nxt = this.peek()) != null) && !(this.is_new_key(nxt))) {
452 | this.get();
453 | key.add_line(nxt);
454 | }
455 | return key;
456 | };
457 |
458 | return Parser;
459 |
460 | })();
461 |
462 | exports.parse = parse = function(txt) {
463 | return new Parser(txt).parse();
464 | };
465 |
466 | exports.list_fingerprints = list_fingerprints = function(txt) {
467 | return parse(txt).fingerprints();
468 | };
469 |
470 | }).call(this);
471 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | // Generated by IcedCoffeeScript 1.7.1-c
2 | (function() {
3 | var k, m, modules, v, _i, _len;
4 |
5 | modules = [require("./err"), require("./gpg"), require("./parse"), require("./colgrep")];
6 |
7 | for (_i = 0, _len = modules.length; _i < _len; _i++) {
8 | m = modules[_i];
9 | for (k in m) {
10 | v = m[k];
11 | exports[k] = v;
12 | }
13 | }
14 |
15 | exports.keyring = require('./keyring');
16 |
17 | }).call(this);
18 |
--------------------------------------------------------------------------------
/lib/parse.js:
--------------------------------------------------------------------------------
1 | // Generated by IcedCoffeeScript 1.7.1-c
2 | (function() {
3 | var E, GPG, Message, Packet, Parser, iced, parse, strip, util, __iced_k, __iced_k_noop;
4 |
5 | iced = require('iced-runtime').iced;
6 | __iced_k = __iced_k_noop = function() {};
7 |
8 | E = require('./err').E;
9 |
10 | GPG = require('./gpg').GPG;
11 |
12 | util = require('util');
13 |
14 | strip = function(x) {
15 | var m;
16 | if ((m = x.match(/^\s*(.*)$/))) {
17 | return m[1];
18 | } else {
19 | return x;
20 | }
21 | };
22 |
23 | Packet = (function() {
24 | function Packet(_arg) {
25 | this.type = _arg.type, this.options = _arg.options;
26 | this._subfields = [];
27 | }
28 |
29 | Packet.prototype.add_subfield = function(f) {
30 | return this._subfields.push(f);
31 | };
32 |
33 | Packet.prototype.subfields = function() {
34 | return this._subfields;
35 | };
36 |
37 | return Packet;
38 |
39 | })();
40 |
41 | Message = (function() {
42 | function Message(_packets) {
43 | this._packets = _packets;
44 | }
45 |
46 | Message.prototype.packets = function() {
47 | return this._packets;
48 | };
49 |
50 | return Message;
51 |
52 | })();
53 |
54 | exports.Parser = Parser = (function() {
55 | function Parser(pgp_output) {
56 | this.pgp_output = pgp_output;
57 | }
58 |
59 | Parser.prototype.run = function() {
60 | this.preprocess();
61 | return new Message(this.parse_packets());
62 | };
63 |
64 | Parser.prototype.preprocess = function() {
65 | var line;
66 | return this._lines = (function() {
67 | var _i, _len, _ref, _results;
68 | _ref = this.pgp_output.split(/\r?\n/);
69 | _results = [];
70 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
71 | line = _ref[_i];
72 | if (line.match(/\S/)) {
73 | _results.push(line);
74 | }
75 | }
76 | return _results;
77 | }).call(this);
78 | };
79 |
80 | Parser.prototype.parse_packets = function() {
81 | var _results;
82 | _results = [];
83 | while (!this.eof()) {
84 | _results.push(this.parse_packet());
85 | }
86 | return _results;
87 | };
88 |
89 | Parser.prototype.peek = function() {
90 | return this._lines[0];
91 | };
92 |
93 | Parser.prototype.get = function() {
94 | return this._lines.shift();
95 | };
96 |
97 | Parser.prototype.eof = function() {
98 | return this._lines.length === 0;
99 | };
100 |
101 | Parser.prototype.parse_packet = function() {
102 | var first, m, packet, rxx;
103 | rxx = /^:([a-zA-Z0-9_ -]+) packet:( (.*))?$/;
104 | first = this.get();
105 | if (!(m = first.match(rxx))) {
106 | throw new E.ParseError("expected ':literal data packet:' style header; got " + first);
107 | }
108 | packet = new Packet({
109 | type: m[1],
110 | options: m[3]
111 | });
112 | while (!(this.eof() || this.peek()[0] === ':')) {
113 | packet.add_subfield(strip(this.get()));
114 | }
115 | return packet;
116 | };
117 |
118 | return Parser;
119 |
120 | })();
121 |
122 | exports.parse = parse = function(_arg, cb) {
123 | var buf, e, err, gpg, message, out, ___iced_passed_deferral, __iced_deferrals, __iced_k;
124 | __iced_k = __iced_k_noop;
125 | ___iced_passed_deferral = iced.findDeferral(arguments);
126 | gpg = _arg.gpg, message = _arg.message;
127 | gpg || (gpg = new GPG);
128 | out = null;
129 | (function(_this) {
130 | return (function(__iced_k) {
131 | __iced_deferrals = new iced.Deferrals(__iced_k, {
132 | parent: ___iced_passed_deferral,
133 | filename: "/Users/max/src/keybase/gpg-wrapper/src/parse.iced"
134 | });
135 | gpg.run({
136 | args: ["--list-packets"],
137 | stdin: message
138 | }, __iced_deferrals.defer({
139 | assign_fn: (function() {
140 | return function() {
141 | err = arguments[0];
142 | return buf = arguments[1];
143 | };
144 | })(),
145 | lineno: 52
146 | }));
147 | __iced_deferrals._fulfill();
148 | });
149 | })(this)((function(_this) {
150 | return function() {
151 | if (typeof err === "undefined" || err === null) {
152 | try {
153 | out = (new Parser(buf.toString('utf8'))).run();
154 | } catch (_error) {
155 | e = _error;
156 | err = e;
157 | }
158 | }
159 | return cb(err, out);
160 | };
161 | })(this));
162 | };
163 |
164 | }).call(this);
165 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gpg-wrapper",
3 | "version": "1.0.6",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "colors": {
8 | "version": "1.3.3",
9 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
10 | "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
11 | "dev": true
12 | },
13 | "commander": {
14 | "version": "2.19.0",
15 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
16 | "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
17 | "dev": true
18 | },
19 | "deep-equal": {
20 | "version": "1.0.1",
21 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
22 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
23 | "dev": true
24 | },
25 | "docco": {
26 | "version": "0.6.3",
27 | "resolved": "https://registry.npmjs.org/docco/-/docco-0.6.3.tgz",
28 | "integrity": "sha1-xHtYI9eVY9b8Or1J895ImG5VIu4=",
29 | "dev": true,
30 | "requires": {
31 | "commander": ">= 0.5.2",
32 | "fs-extra": ">= 0.6.0",
33 | "highlight.js": ">= 8.0.x",
34 | "marked": ">= 0.2.7",
35 | "underscore": ">= 1.0.0"
36 | }
37 | },
38 | "fs-extra": {
39 | "version": "7.0.1",
40 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
41 | "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
42 | "dev": true,
43 | "requires": {
44 | "graceful-fs": "^4.1.2",
45 | "jsonfile": "^4.0.0",
46 | "universalify": "^0.1.0"
47 | }
48 | },
49 | "graceful-fs": {
50 | "version": "4.1.15",
51 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
52 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
53 | "dev": true
54 | },
55 | "highlight.js": {
56 | "version": "9.14.2",
57 | "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.14.2.tgz",
58 | "integrity": "sha512-Nc6YNECYpxyJABGYJAyw7dBAYbXEuIzwzkqoJnwbc1nIpCiN+3ioYf0XrBnLiyyG0JLuJhpPtt2iTSbXiKLoyA==",
59 | "dev": true
60 | },
61 | "iced-coffee-script": {
62 | "version": "1.7.1-c",
63 | "resolved": "https://registry.npmjs.org/iced-coffee-script/-/iced-coffee-script-1.7.1-c.tgz",
64 | "integrity": "sha1-OGqbyhXvVBhDyLJdHhBTHaHj9wk=",
65 | "dev": true,
66 | "requires": {
67 | "docco": "~0.6.2",
68 | "iced-runtime": ">=0.0.1",
69 | "mkdirp": "~0.3.5"
70 | }
71 | },
72 | "iced-error": {
73 | "version": "0.0.13",
74 | "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.13.tgz",
75 | "integrity": "sha512-yEEaG8QfyyRL0SsbNNDw3rVgTyqwHFMCuV6jDvD43f/2shmdaFXkqvFLGhDlsYNSolzYHwVLM/CrXt9GygYopA=="
76 | },
77 | "iced-lock": {
78 | "version": "1.1.0",
79 | "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-1.1.0.tgz",
80 | "integrity": "sha1-YRbvHKs6zW5rEIk7snumIv0/3nI=",
81 | "requires": {
82 | "iced-runtime": "^1.0.0"
83 | }
84 | },
85 | "iced-runtime": {
86 | "version": "1.0.3",
87 | "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.3.tgz",
88 | "integrity": "sha1-LU9PuZmreqVDCxk8d6f85BGDGc4="
89 | },
90 | "iced-spawn": {
91 | "version": "1.0.2",
92 | "resolved": "https://registry.npmjs.org/iced-spawn/-/iced-spawn-1.0.2.tgz",
93 | "integrity": "sha512-wcHVObZWqBjSc8CcS2Lwojjs59aNenhFAT8941h0msMOviEQeoKYr2RgAX8dPvUYcOlGTYc3kf8cNOdc9DMoqg==",
94 | "requires": {
95 | "iced-runtime": ">=0.0.1",
96 | "semver": ">=2.2.1"
97 | }
98 | },
99 | "iced-test": {
100 | "version": "0.0.27",
101 | "resolved": "https://registry.npmjs.org/iced-test/-/iced-test-0.0.27.tgz",
102 | "integrity": "sha512-Qm/u3l0rLd97WZvNtBA1V6S3u6klUbNGdyTlwW8hRjUMxpWg4T2FKUboxkO51znbfq+63byFBrbAoWxCIMRCUw==",
103 | "dev": true,
104 | "requires": {
105 | "colors": ">=0.6.2",
106 | "deep-equal": ">=0.2.1",
107 | "iced-runtime": "^1.0.3",
108 | "minimist": ">=0.0.8"
109 | }
110 | },
111 | "iced-utils": {
112 | "version": "0.1.27",
113 | "resolved": "https://registry.npmjs.org/iced-utils/-/iced-utils-0.1.27.tgz",
114 | "integrity": "sha512-Yi17vdjWBXfdqYEA08mCVy1AcLCkj7UsUg3/wCJUMeE5/ZWilc2vhM7nlFpRXpa9LYgbPbg8fNAnP5V8L+TZdw==",
115 | "requires": {
116 | "iced-error": "^0.0.10",
117 | "iced-lock": "^1.0.2",
118 | "iced-runtime": ">=0.0.1 <2.0.0-0"
119 | },
120 | "dependencies": {
121 | "iced-error": {
122 | "version": "0.0.10",
123 | "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.10.tgz",
124 | "integrity": "sha1-+C5DaPefSv3z7SA3WswoqHAoCeg="
125 | }
126 | }
127 | },
128 | "jsonfile": {
129 | "version": "4.0.0",
130 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
131 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
132 | "dev": true,
133 | "requires": {
134 | "graceful-fs": "^4.1.6"
135 | }
136 | },
137 | "marked": {
138 | "version": "0.6.2",
139 | "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.2.tgz",
140 | "integrity": "sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==",
141 | "dev": true
142 | },
143 | "minimist": {
144 | "version": "1.2.2",
145 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz",
146 | "integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==",
147 | "dev": true
148 | },
149 | "mkdirp": {
150 | "version": "0.3.5",
151 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
152 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=",
153 | "dev": true
154 | },
155 | "pgp-utils": {
156 | "version": "0.0.34",
157 | "resolved": "https://registry.npmjs.org/pgp-utils/-/pgp-utils-0.0.34.tgz",
158 | "integrity": "sha1-2E9J98GTteC5QV9cxcKmle15DCM=",
159 | "requires": {
160 | "iced-error": ">=0.0.8",
161 | "iced-runtime": ">=0.0.1"
162 | }
163 | },
164 | "semver": {
165 | "version": "5.6.0",
166 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
167 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
168 | },
169 | "spotty": {
170 | "version": "1.0.0",
171 | "resolved": "https://registry.npmjs.org/spotty/-/spotty-1.0.0.tgz",
172 | "integrity": "sha1-BbtRUrPdB0SjQXZNtfz45HlD5ng=",
173 | "requires": {
174 | "iced-error": "0.0.9",
175 | "iced-runtime": "^1.0.2"
176 | },
177 | "dependencies": {
178 | "iced-error": {
179 | "version": "0.0.9",
180 | "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.9.tgz",
181 | "integrity": "sha1-x8MFdhTAoYfZaz0YxtUg5rhy7Tc="
182 | }
183 | }
184 | },
185 | "underscore": {
186 | "version": "1.9.1",
187 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
188 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==",
189 | "dev": true
190 | },
191 | "universalify": {
192 | "version": "0.1.2",
193 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
194 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
195 | "dev": true
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gpg-wrapper",
3 | "version": "1.0.6",
4 | "description": "A small wrapper around the gpg CLI",
5 | "main": "lib/main.js",
6 | "scripts": {
7 | "test": "make test"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/keybase/gpg-wrapper"
12 | },
13 | "dependencies": {
14 | "iced-error": ">=0.0.9",
15 | "iced-runtime": ">=0.0.1",
16 | "iced-spawn": ">=0.0.8",
17 | "iced-utils": ">=0.1.16",
18 | "pgp-utils": ">=0.0.21",
19 | "spotty": "^1.0.0"
20 | },
21 | "devDependencies": {
22 | "iced-coffee-script": "1.7.1-c",
23 | "iced-test": ">=0.0.9",
24 | "minimist": "~1.2.2"
25 | },
26 | "author": "Maxwell Krohn ",
27 | "license": "BSD-3-Clause",
28 | "bugs": {
29 | "url": "https://github.com/keybase/gpg-wrapper/issues"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/colgrep.iced:
--------------------------------------------------------------------------------
1 |
2 | ##=======================================================================
3 |
4 | exports.colgrep = colgrep = ({patterns, buffer, separator}) ->
5 | separator or= /:/
6 | lines = buffer.toString('utf8').split '\n'
7 | indices = (parseInt(k) for k,v of patterns)
8 | max_index = Math.max indices...
9 | out = []
10 | for line in lines when (cols = line.split separator)? and (max_index < cols.length)
11 | found = true
12 | for k,v of patterns
13 | unless cols[k].match v
14 | found = false
15 | break
16 | out.push cols if found
17 | return out
18 |
19 | ##=======================================================================
20 |
--------------------------------------------------------------------------------
/src/err.iced:
--------------------------------------------------------------------------------
1 |
2 | ie = require 'iced-error'
3 |
4 | #================================================
5 |
6 | exports.E = ie.make_errors
7 | GPG : "Command line error"
8 | PGP_ID_COLLISION : "PGP ID collision error"
9 | NOT_FOUND : "Key wasn't found"
10 | CMD : "Non-zero exit code"
11 | PARSE : "parse error"
12 | VERIFY : "Signature verification error"
13 | NO_FINGERPRINT : "No fingerprint found"
14 |
--------------------------------------------------------------------------------
/src/gpg.iced:
--------------------------------------------------------------------------------
1 |
2 | {colgrep} = require './colgrep'
3 | {E} = require './err'
4 | {parse} = require('pgp-utils').userid
5 | ispawn = require 'iced-spawn'
6 | {make_esc} = require 'iced-error'
7 | spotty = require 'spotty'
8 |
9 | ##=======================================================================
10 |
11 | _gpg_cmd = "gpg"
12 | exports.set_gpg_cmd = set_gpg_cmd = (c) -> _gpg_cmd = c
13 | exports.get_gpg_cmd = ( ) -> _gpg_cmd
14 |
15 | # A default log for uncaught stderr
16 | _log = null
17 | exports.set_log = (l) -> _log = l
18 |
19 | ##=======================================================================
20 |
21 | exports.find_and_set_cmd = (cmd, cb) ->
22 | if cmd?
23 | await (new GPG { cmd }).test defer err, v
24 | if err?
25 | err = new Error "Could not access the supplied GPG command '#{cmd}'"
26 | else
27 | cmds = [ "gpg2", "gpg" ]
28 | for cmd in cmds
29 | await (new GPG { cmd }).test defer err, v
30 | break unless err
31 | if err?
32 | err = new Error "Could not find GPG command: tried 'gpg2' and 'gpg'"
33 | set_gpg_cmd(cmd) unless err?
34 | cb err, v, cmd
35 |
36 | ##=======================================================================
37 |
38 | _tty = null
39 |
40 | exports.pinentry_init = (cb) ->
41 | await spotty.tty defer err, tmp
42 | _tty = tmp
43 | cb err, _tty
44 |
45 | ##=======================================================================
46 |
47 | exports.GPG = class GPG
48 |
49 | #----
50 |
51 | constructor : (opts) ->
52 | @CMD = if (c = opts?.cmd)? then c else _gpg_cmd
53 |
54 | #----
55 |
56 | mutate_args : (args) ->
57 |
58 | #----
59 |
60 | test : (cb) ->
61 | await ispawn.run { name : @CMD, args : [ "--version" ], quiet : true }, defer err, out
62 | cb err, out
63 |
64 | #----
65 |
66 | run : (inargs, cb) ->
67 | stderr = null
68 | @mutate_args inargs
69 | env = process.env
70 | delete env.LANGUAGE
71 | env.GPG_TTY = _tty if _tty?
72 | inargs.name = @CMD
73 | inargs.eklass = E.GpgError
74 | inargs.opts = { env }
75 | inargs.log = _log if _log?
76 | inargs.stderr = stderr = new ispawn.BufferOutStream() if not inargs.stderr? and inargs.quiet
77 | inargs.args = [ "--no-options"].concat(inargs.args) if inargs.no_options
78 | await ispawn.run inargs, defer err, out
79 | if err? and stderr?
80 | err.stderr = stderr.data()
81 | cb err, out
82 |
83 | #----
84 |
85 | command_line : (inargs) ->
86 | @mutate_args inargs
87 | v = [ @CMD ].concat inargs.args
88 | v.join(" ")
89 |
90 | #----
91 |
92 | assert_no_collision : (id, cb) ->
93 | args = [ "-k", "--with-colons", id ]
94 | n = 0
95 | await @run { args, quiet : true } , defer err, out
96 | if err? then # noop
97 | else
98 | rows = colgrep {
99 | patterns : {
100 | 0 : /^[sp]ub$/
101 | 4 : (new RegExp "^.*#{id}$", "i")
102 | },
103 | buffer : out,
104 | separator : /:/
105 | }
106 | if (n = rows.length) > 1
107 | err = new E.PgpIdCollisionError "Found two keys for ID=#{short_id}"
108 | cb err, n
109 |
110 | #----
111 |
112 | assert_exactly_one : (short_id, cb) ->
113 | await @assert_no_collision short_id, defer err, n
114 | err = new E.NotFoundError "Didn't find a key for #{short_id}" unless n is 1
115 | cb err
116 |
117 | ##=======================================================================
118 |
119 |
--------------------------------------------------------------------------------
/src/index.iced:
--------------------------------------------------------------------------------
1 |
2 | pgpu = require('pgp-utils').userid
3 | {Warnings} = require('iced-utils').util
4 | util = require 'util'
5 |
6 | #==========================================================
7 |
8 | class BucketDict
9 |
10 | constructor : () ->
11 | @_d = {}
12 |
13 | add : (k,v) ->
14 | k = ("" + k).toLowerCase()
15 | @_d[k] = b = [] unless (b = @_d[k])?
16 | b.push v
17 |
18 | get : (k) -> @_d[("" + k).toLowerCase()] or []
19 |
20 | get_0_or_1 : (k) ->
21 | l = @get(k)
22 | err = obj = null
23 | if (n = l.length) > 1
24 | err = new Error "wanted a unique lookup, but got #{n} object for key #{k}"
25 | else
26 | obj = if n is 0 then null else l[0]
27 | return [err,obj]
28 |
29 | #==========================================================
30 |
31 | uniquify = (v) ->
32 | h = {}
33 | (h[e] = true for e in v)
34 | (k for k of h)
35 |
36 | #==========================================================
37 |
38 | class Index
39 |
40 | constructor : () ->
41 | @_keys = []
42 | @_lookup =
43 | email : new BucketDict()
44 | fingerprint : new BucketDict()
45 | key_id_64 : new BucketDict()
46 |
47 | push_element : (el) ->
48 | if (k = el.to_key()) then @index_key k
49 |
50 | index_key : (k) ->
51 | @_keys.push k
52 | @_lookup.fingerprint.add(k.fingerprint(), k)
53 | for e in uniquify(k.emails())
54 | @_lookup.email.add(e, k)
55 | for i in k.all_key_id_64s()
56 | @_lookup.key_id_64.add(i, k)
57 |
58 | lookup : () -> @_lookup
59 | keys : () -> @_keys
60 | fingerprints : () -> (k.fingerprint() for k in @keys())
61 |
62 | #==========================================================
63 |
64 | class Element
65 | constructor : () ->
66 | @_err = null
67 | err : () -> @_err
68 | is_ok : () -> not @_err?
69 | to_key : () -> null
70 |
71 | #==========================================================
72 |
73 | parse_int = (s) -> if s?.match /^[0-9]+$/ then parseInt(s, 10) else s
74 |
75 | #==========================================================
76 |
77 | class BaseKey extends Element
78 |
79 | constructor : (line) ->
80 | super()
81 | if line.v.length < 12
82 | @_err = new Error "Key is malformed; needs at least 12 fields"
83 | else
84 | v = (parse_int(e) for e in line.v)
85 | [ @_pub, @_trust, @_n_bits, @_type, @_key_id_64, @_created, @_expires ] = v
86 |
87 | err : () -> @_err
88 | to_key : () -> null
89 | key_id_64 : () -> @_key_id_64
90 | fingerprint : () -> @_fingerprint
91 | add_fingerprint : (line) -> @_fingerprint = line.get(9)
92 | is_revoked : () -> @_trust is 'r'
93 | to_dict : ({secret}) -> {
94 | fingerprint : @fingerprint(),
95 | key_id_64 : @key_id_64(),
96 | secret : secret,
97 | is_revoked : @is_revoked()
98 | }
99 |
100 | #==========================================================
101 |
102 | class Subkey extends BaseKey
103 |
104 |
105 | #==========================================================
106 |
107 | class Key extends BaseKey
108 |
109 | constructor : (line) ->
110 | super line
111 | @_userids = []
112 | @_subkeys = []
113 | @_top = @
114 | if @is_ok()
115 | @add_uid line
116 |
117 | emails : () -> (e for u in @_userids when (e = u.email)? )
118 | to_key : () -> @
119 | userids : () -> @_userids
120 | subkeys : () -> @_subkeys
121 |
122 | to_dict : (d) ->
123 | r = super d
124 | r.uid = @userids()[0]
125 | r.all_uids = @userids
126 | return r
127 |
128 | all_keys : () -> [ @ ].concat(@_subkeys)
129 |
130 | all_key_id_64s : () ->
131 | ret = (i for s in @all_keys() when (i = s.key_id_64())?)
132 | return ret
133 |
134 | add_line : (line) ->
135 | err = null
136 | if (n = line.v.length) < 2
137 | line.warn "got too few fields (#{n})"
138 | else
139 | switch (f = line.v[0])
140 | when 'fpr' then @_top.add_fingerprint line
141 | when 'uid' then @add_uid line
142 | when 'uat' then # skip user attributes
143 | when 'sub', 'ssb' then @add_subkey line
144 | else
145 | line.warn "unexpected subfield: #{f}"
146 |
147 | add_subkey : (line) ->
148 | key = new Subkey line
149 | if key.is_ok()
150 | @_subkeys.push key
151 | @_top = key
152 | else
153 | line.warn "Bad subkey: #{key.err().message}"
154 |
155 | add_uid : (line) ->
156 | if (e = line.get(9))? and (u = pgpu.parse(e))?
157 | @_userids.push u
158 |
159 | #==========================================================
160 |
161 | class Ignored extends Element
162 |
163 | constructor : (line) ->
164 |
165 | #==========================================================
166 |
167 | class Line
168 | constructor : (txt, @number, @parser) ->
169 | @v = txt.split(":")
170 | if @v.length < 2
171 | @warn "Bad line; expectect at least 2 fields"
172 | warn : (m) -> @parser.warn(@number + ": " + m)
173 | get : (n) ->
174 | if (n < @v.length and @v[n].length) then @v[n] else null
175 |
176 | #==========================================================
177 |
178 | exports.Parser = class Parser
179 |
180 | #-----------------------
181 |
182 | constructor : (@txt) ->
183 | @_warnings = new Warnings()
184 | @init()
185 |
186 | #-----------------------
187 |
188 | warn : (w) -> @_warnings.push w
189 | warnings : () -> @_warnings
190 |
191 | #-----------------------
192 |
193 | init : () -> @lines = (new Line(l,i+1,@) for l,i in @txt.split(/\r?\n/) when l.length > 0)
194 | peek : () -> if @is_eof() then null else @lines[0]
195 | get : () -> if @is_eof() then null else @lines.shift()
196 | is_eof : () -> (@lines.length is 0)
197 |
198 | #-----------------------
199 |
200 | parse_ignored : (line) -> return new Ignored line
201 |
202 | #-----------------------
203 |
204 | parse : () ->
205 | index = new Index()
206 | until @is_eof()
207 | index.push_element(element) if (element = @parse_element()) and element.is_ok()
208 | return index
209 |
210 | #-----------------------
211 |
212 | is_new_key : (line) -> line? and (line.get(0) in ['pub', 'sec'])
213 |
214 | #-----------------------
215 |
216 | parse_element : () ->
217 | line = @get()
218 | if @is_new_key(line) then @parse_key line
219 | else @parse_ignored line
220 |
221 | #-----------------------
222 |
223 | parse_key : (first_line) ->
224 | key = new Key first_line
225 | while (nxt = @peek())? and not(@is_new_key(nxt))
226 | @get()
227 | key.add_line(nxt)
228 | return key
229 |
230 | #==========================================================
231 |
232 | exports.parse = parse = (txt) -> (new Parser(txt).parse())
233 | exports.list_fingerprints = list_fingerprints = (txt) -> parse(txt).fingerprints()
234 |
235 | #==========================================================
236 |
237 |
--------------------------------------------------------------------------------
/src/keyring.iced:
--------------------------------------------------------------------------------
1 |
2 | {GPG} = require './gpg'
3 | {chain,make_esc} = require 'iced-error'
4 | {mkdir_p} = require('iced-utils').fs
5 | {prng} = require 'crypto'
6 | pgp_utils = require('pgp-utils')
7 | {trim,fingerprint_to_key_id_64,fpeq,athrow,base64u} = pgp_utils.util
8 | {userid} = pgp_utils
9 | {E} = require './err'
10 | path = require 'path'
11 | fs = require 'fs'
12 | {colgrep} = require './colgrep'
13 | util = require 'util'
14 | os = require 'os'
15 | {a_json_parse} = require('iced-utils').util
16 | {list_fingerprints,Parser} = require('./index')
17 |
18 | ##=======================================================================
19 |
20 | strip = (m) -> m.split(/\s+/).join('')
21 |
22 | states =
23 | NONE : 0
24 | LOADED : 1
25 | SAVED : 2
26 |
27 | ##=======================================================================
28 |
29 | exports.Log = class Log
30 | constructor : ->
31 | debug : (x) -> console.error x
32 | warn : (x) -> console.error x
33 | error : (x) -> console.error x
34 | info : (x) -> console.error x
35 |
36 | ##=======================================================================
37 |
38 | exports.Globals = class Globals
39 |
40 | constructor : ({@get_preserve_tmp_keyring,
41 | @get_debug,
42 | @get_tmp_keyring_dir,
43 | @get_key_klass,
44 | @get_home_dir,
45 | @get_gpg_cmd,
46 | @get_no_options,
47 | @log}) ->
48 | @get_preserve_tmp_keyring or= () -> false
49 | @log or= new Log
50 | @get_debug or= () -> false
51 | @get_tmp_keyring_dir or= () -> os.tmpdir()
52 | @get_key_klass or= () -> GpgKey
53 | @get_home_dir or= () -> null
54 | @get_gpg_cmd or= () -> null
55 | @get_no_options or= () -> false
56 | @_mring = null
57 |
58 | master_ring : () -> @_mring
59 | set_master_ring : (r) -> @_mring = r
60 |
61 | #----------------
62 |
63 | _globals = new Globals {}
64 | globals = () -> _globals
65 |
66 | #----------------
67 |
68 | exports.init = (d) ->
69 | _globals = new Globals d
70 | _globals.set_master_ring new MasterKeyRing()
71 |
72 | #----------------
73 |
74 | log = () -> globals().log
75 |
76 | ##=======================================================================
77 |
78 | exports.GpgKey = class GpgKey
79 |
80 | #-------------
81 |
82 | constructor : (fields) ->
83 | @_state = states.NONE
84 | for k,v of fields
85 | @["_#{k}"] = v
86 |
87 | #-------------
88 |
89 | # The fingerprint of the key
90 | fingerprint : () -> @_fingerprint
91 |
92 | # The 64-bit GPG key ID
93 | key_id_64 : () -> @_key_id_64 or (if @fingerprint() then @fingerprint()[-16...] else null)
94 |
95 | # Something to load a key by
96 | load_id : () -> @key_id_64() or @fingerprint() or @username()
97 |
98 | # The keybase username of the keyholder
99 | username : () -> @_username
100 |
101 | # The keybase UID of the keyholder
102 | uid : () -> @_uid
103 |
104 | # All uids, available after a load from the keyring
105 | all_uids : () -> @_all_uids
106 |
107 | # Return the raw armored PGP key data
108 | key_data : () -> @_key_data
109 |
110 | # The keyring object that we've wrapped in
111 | keyring : () -> @_keyring
112 |
113 | # These three functions are to fulfill to key manager interface
114 | get_pgp_key_id : () -> @key_id_64()
115 | get_pgp_fingerprint : () -> @fingerprint().toLowerCase()
116 | get_ekid : () -> null
117 |
118 | is_signed : () -> !!@_is_signed
119 |
120 | #-------------
121 |
122 | check_is_signed : (signer, cb) ->
123 | log().debug "+ Check if #{signer.to_string()} signed #{@to_string()}"
124 | id = @load_id()
125 | args = [ "--list-sigs", "--with-colons", id ]
126 | await @gpg { args }, defer err, out
127 | unless err?
128 | rows = colgrep { buffer : out, patterns : { 0 : /^sig$/ }, separator : /:/ }
129 | to_find = signer.key_id_64().toUpperCase()
130 | for row in rows
131 | if row[4] is to_find
132 | log().debug "| Found in row: #{JSON.stringify row}"
133 | @_is_signed = true
134 | break
135 | log().debug "- Check -> #{@_is_signed}"
136 | cb err, @_is_signed
137 |
138 | #-------------
139 |
140 | # Find the key in the keyring based on fingerprint
141 | find : (cb) ->
142 | if (fp = @fingerprint())?
143 | args = [ "-" + (if @_secret then 'K' else 'k'), "--with-colons", fp ]
144 | await @gpg { args, quiet : true }, defer err, out
145 | if err?
146 | err = new E.NotFoundError "Key for #{@to_string()} not found"
147 | else
148 | err = new E.NoFingerprintError "No fingerprint given for #{@_username}"
149 | cb err
150 |
151 | #-------------
152 |
153 | # Check that this key has been signed by the signing key.
154 | check_sig : (signing_key, cb) ->
155 | args = [ '--list-sigs', '--with-colon', @fingerprint() ]
156 | await @gpg { args }, defer err, out
157 | unless err?
158 | rows = colgrep { buffer : out, patterns : {
159 | 0 : /^sub$/
160 | 4 : (new RegExp "^#{signing_key.key_id_64()}$", "i")
161 | }
162 | }
163 | if rows.length is 0
164 | err = new E.VerifyError "No signature of #{@to_string()} by #{signing_key.to_string()}"
165 | cb err
166 |
167 | #-------------
168 |
169 | set_keyring : (r) -> @_keyring = r
170 |
171 | #-------------
172 |
173 | to_string : () -> [ @username(), @key_id_64() ].join "/"
174 |
175 | #-------------
176 |
177 | gpg : (gargs, cb) -> @keyring().gpg gargs, cb
178 |
179 | #-------------
180 |
181 | # Save this key to the underlying GPG keyring
182 | save : (cb) ->
183 | args = [ "--import" ]
184 | args.push "--import-options", "import-local-sigs" if @_secret
185 | log().debug "| Save key #{@to_string()} to #{@keyring().to_string()}"
186 | await @gpg { args, stdin : @_key_data, quiet : true, secret : @_secret, no_options : true }, defer err
187 | @_state = states.SAVED
188 | cb err
189 |
190 | #-------------
191 |
192 | # Load this key from the underlying GPG keyring
193 | load : (cb) ->
194 | id = @load_id()
195 | esc = make_esc cb, "GpgKey::load"
196 | args = [
197 | (if @_secret then "--export-secret-key" else "--export" ),
198 | "--export-options", "export-local-sigs",
199 | "-a",
200 | id
201 | ]
202 | log().debug "| Load key #{@to_string()} from #{@keyring().to_string()} (secret=#{@_secret})"
203 | await @gpg { args }, esc defer @_key_data
204 |
205 | if not @fingerprint()?
206 | log().debug "+ lookup fingerprint"
207 | args = [ "-k", "--fingerprint", "--with-colons", id ]
208 | await @gpg { args, no_options : true }, esc defer out
209 | fp = list_fingerprints out.toString('utf8')
210 | if (l = fp.length) is 0
211 | err = new E.GpgError "Couldn't find GPG fingerprint for #{id}"
212 | else if l > 1
213 | err = new E.GpgError "Found more than one (#l) keys for #{id}"
214 | else
215 | @_state = states.LOADED
216 | @_fingerprint = fp[0]
217 | log().debug "- Map #{id} -> #{@_fingerprint} via gpg"
218 |
219 | if not @uid()?
220 | log().debug "+ lookup UID"
221 | await @read_uids_from_key esc defer uids
222 | l = uids.length
223 | if l is 0
224 | log().debug "| weird; no UIDs found"
225 | else
226 | log().debug "| got back more than one UID; using the first: (#{JSON.stringify uids})" if l > 1
227 | @_uid = uids[0]
228 | log().debug " - Map #{id} -> #{@_uid} via gpg"
229 | @_all_uids = uids
230 | log().debug "- looked up UID"
231 | cb err
232 |
233 | #-------------
234 |
235 | # Remove this key from the keyring
236 | remove : (cb) ->
237 | args = [
238 | (if @_secret then "--delete-secret-and-public-key" else "--delete-keys"),
239 | "--batch",
240 | "--yes",
241 | @fingerprint()
242 | ]
243 | log().debug "| Delete key #{@to_string()} from #{@keyring().to_string()}"
244 | await @gpg { args }, defer err
245 | cb err
246 |
247 | #-------------
248 |
249 | # Read the userIds that have been signed with this key
250 | read_uids_from_key : (cb) ->
251 | fp = @fingerprint()
252 | log().debug "+ read_uids_from_keys #{fp}"
253 | uids = null
254 | if (uids = @_my_userids)?
255 | log().debug "| hit cache"
256 | else
257 | args = { fingerprint : fp }
258 | await @keyring().read_uids_from_key args, defer err, tmp
259 | uids = @_my_userids = tmp
260 | log().debug "| got: #{if uids? then JSON.stringify(uids) else null}"
261 | log().debug "- read_uids_from_key -> #{err}"
262 | cb err, uids
263 |
264 | #-------------
265 |
266 | sign_key : ({signer}, cb) ->
267 | log().debug "| GPG-signing #{@username()}'s key with your key"
268 | args = ["--sign-key", "--batch", "--yes" ]
269 | skip = false
270 | err = null
271 | if signer?
272 | args.push "-u", signer.fingerprint()
273 | else
274 | await @keyring().has_signing_key defer err, hsk
275 | if err? then skip = true
276 | else if not hsk
277 | log().info "Not trying to sign key #{@to_string()} since there's no signing key available"
278 | skip = true
279 | unless skip
280 | args.push @fingerprint()
281 | await @gpg { args, quiet : true }, defer err
282 | cb err
283 |
284 | #-------------
285 |
286 | # Assuming this is a temporary key, commit it to the master key chain, after signing it
287 | commit : ({signer, sign_key, ring }, cb) ->
288 | esc = make_esc cb, "GpgKey::commit"
289 | try_sign = sign_key or signer?
290 | if @keyring().is_temporary()
291 | log().debug "+ #{@to_string()}: Commit temporary key"
292 | await @sign_key { signer }, esc defer() if try_sign
293 | await @load esc defer()
294 | await @remove esc defer()
295 | ring or= master_ring()
296 | await (@copy_to_keyring ring).save esc defer()
297 | log().debug "- #{@to_string()}: Commit temporary key"
298 | else if not @_is_signed
299 | if try_sign
300 | log().debug "| #{@to_string()}: signing key, since it wasn't signed"
301 | await @sign_key {signer}, esc defer()
302 | else
303 | log().debug "| #{@to_string()}: key wasn't signed, but signing was skipping"
304 | else
305 | log().debug "| #{@to_string()}: key was previously commited; noop"
306 | cb null
307 |
308 | #-------------
309 |
310 | rollback : (cb) ->
311 | s = @to_string()
312 | err = null
313 | if globals().get_preserve_tmp_keyring() and @keyring().is_temporary()
314 | log().debug "| #{s}: preserving temporary keyring by command-line flag"
315 | else if @keyring().is_temporary()
316 | log().debug "| #{s}: Rolling back temporary key"
317 | await @remove defer err
318 | else
319 | log().debug "| #{s}: no need to rollback key, it's permanent"
320 | cb err
321 |
322 | #-------------
323 |
324 | to_data_dict : () ->
325 | d = {}
326 | d[k[1...]] = v for k,v of @ when k[0] is '_'
327 | return d
328 |
329 | #-------------
330 |
331 | copy_to_keyring : (keyring) ->
332 | return keyring.make_key @to_data_dict()
333 |
334 | #--------------
335 |
336 | _find_key_in_stderr : (which, buf) ->
337 | err = ki64 = fingerprint = null
338 | d = buf.toString('utf8')
339 | if (m = d.match(/Primary key fingerprint: (.*)/))? then fingerprint = m[1]
340 | else if (m = d.match(/using [RD]SA key ([A-F0-9]{16})/))? then ki64 = m[1]
341 | else err = new E.VerifyError "#{which}: can't parse PGP output in verify signature"
342 | return { err, ki64, fingerprint }
343 |
344 | #--------------
345 |
346 | _verify_key_id_64 : ( {ki64, which, sig}, cb) ->
347 | log().debug "+ GpgKey::_verify_key_id_64: #{which}: #{ki64} vs #{@fingerprint()}"
348 | err = null
349 | if ki64 isnt @key_id_64()
350 | await @gpg { args : [ "--fingerprint", "--keyid-format", "long", ki64 ] }, defer err, out
351 | if err? then # noop
352 | else if not (m = out.toString('utf8').match(/Key fingerprint = ([A-F0-9 ]+)/) )?
353 | err = new E.VerifyError "Querying for a fingerprint failed"
354 | else if not (a = strip(m[1])) is (b = @fingerprint())
355 | err = new E.VerifyError "Fingerprint mismatch: #{a} != #{b}"
356 | else
357 | log().debug "| Successful map of #{ki64} -> #{@fingerprint()}"
358 |
359 | unless err?
360 | await @keyring().assert_no_collision ki64, defer err
361 |
362 | log().debug "- GpgKey::_verify_key_id_64: #{which}: #{ki64} vs #{@fingerprint()} -> #{err}"
363 | cb err
364 |
365 | #-------------
366 |
367 | verify_sig : ({which, sig, payload}, cb) ->
368 | log().debug "+ GpgKey::verify_sig #{which}"
369 | err = null
370 | args =
371 | query : @fingerprint()
372 | single : true
373 | sig : sig
374 | no_json : true
375 | keyblock : @_key_data
376 | secret : @_secret
377 | await @keyring().oneshot_verify args, defer err, out, fp
378 |
379 | if not err? and not(@_fingerprint?) and fp?
380 | @_fingerprint = fp
381 | log().debug "| Setting fingerprint to #{fp} as a result of oneshot_verify"
382 |
383 | # Check that the signature verified, and that the intended data came out the other end
384 | msg = if err? then "signature verification failed"
385 | else if @_fingerprint? and (@_fingerprint.toLowerCase() isnt fp.toLowerCase())
386 | "Wrong key fingerprint; was the server lying? #{@_fingerprint} != #{fp}"
387 | else if ((a = trim(out.toString('utf8'))) isnt (b = trim(payload))) then "wrong payload: #{a} != #{b} (#{a.length} v #{b.length})"
388 | else null
389 |
390 | # If there's an exception, we can now throw out of this function
391 | if msg? then err = new E.VerifyError "#{which}: #{msg}"
392 |
393 | log().debug "- GpgKey::verify_sig #{which} -> #{err}"
394 | cb err
395 |
396 | ##=======================================================================
397 |
398 | exports.BaseKeyRing = class BaseKeyRing extends GPG
399 |
400 | constructor : () ->
401 | super { cmd : globals().get_gpg_cmd() }
402 | @_has_signing_key = null
403 |
404 | #------
405 |
406 | has_signing_key : (cb) ->
407 | err = null
408 | unless @_has_signing_key?
409 | await @find_secret_keys {}, defer err, ids
410 | if err?
411 | log().warn "Issue listing secret keys: #{err.message}"
412 | else
413 | @_has_signing_key = (ids.length > 0)
414 | cb err, @_has_signing_key
415 |
416 | #------
417 |
418 | make_key : (opts) ->
419 | klass = globals().get_key_klass()
420 | ret = new klass opts
421 | ret.set_keyring @
422 | return ret
423 |
424 | #------
425 |
426 | is_temporary : () -> false
427 | tmp_dir : () -> os.tmpdir()
428 |
429 | #----------------------------
430 |
431 | make_oneshot_ring_2 : ({keyblock, single, secret}, cb) ->
432 | esc = make_esc cb, "BaseKeyRing::_make_oneshot_ring_2"
433 | await @gpg { no_options : true, args : [ "--import"], stdin : keyblock, quiet : true, secret }, esc defer()
434 | await @list_fingerprints esc defer fps
435 | n = fps.length
436 | err = if n is 0 then new E.NotFoundError "key import failed"
437 | else if single and n > 1 then new E.PgpIdCollisionError "too many keys found: #{n}"
438 | else
439 | @_fingerprint = fps[0]
440 | # Eventually use PGP-utils for this, but for now....
441 | @_key_id_64 = @_fingerprint[-16...]
442 | null
443 | cb err, @_fingerprint
444 |
445 | #----------------------------
446 |
447 | make_oneshot_ring : ({query, single, keyblock, secret}, cb) ->
448 | esc = make_esc cb, "BaseKeyRing::make_oneshot_ring"
449 | unless keyblock?
450 | args = [ "-a", "--export" , query ]
451 | await @gpg { args, no_options : true }, esc defer keyblock
452 | await TmpOneShotKeyRing.make esc defer ring
453 | await ring.make_oneshot_ring_2 { keyblock, single, secret }, defer err, fp
454 | if err?
455 | await ring.nuke defer e2
456 | log().warn "Error cleaning up keyring after failure: #{e2.message}" if e2?
457 | cb err, ring, fp
458 |
459 | #----------------------------
460 |
461 | find_keys_full : ( {query, secret, sigs}, cb) ->
462 | args = [ "--with-colons", "--fingerprint" ]
463 | args.push if secret then "-K" else "-k"
464 | args.push query if query
465 | await @gpg { args, list_keys : true, no_options : true }, defer err, out
466 | res = null
467 | unless err?
468 | index = (new Parser out.toString('utf8')).parse()
469 | res = (@make_key(k.to_dict({secret})) for k in index.keys())
470 | cb err, res
471 |
472 | #----------------------------
473 |
474 | find_keys : ({query}, cb) ->
475 | args = [ "-k", "--with-colons" ]
476 | args.push query if query
477 | await @gpg { args, list_keys : true, no_options : true }, defer err, out
478 | id64s = null
479 | unless err?
480 | rows = colgrep { buffer : out, patterns : { 0 : /^pub$/ }, separator : /:/ }
481 | id64s = (row[4] for row in rows)
482 | cb err, id64s
483 |
484 | #----------------------------
485 |
486 | find_secret_keys : ({query}, cb) ->
487 | args = [ "-K", "--with-colons" ]
488 | args.push query if query
489 |
490 | # Don't give 'list_keys : false' since we want to check both keyrings.
491 | await @gpg { args }, defer err, out
492 |
493 | id64s = null
494 | unless err?
495 | rows = colgrep { buffer : out, patterns : { 0 : /^sec$/ }, separator : /:/ }
496 | id64s = (row[4] for row in rows)
497 | cb err, id64s
498 |
499 | #----------------------------
500 |
501 | list_fingerprints : (cb) ->
502 | await @gpg { args : [ "--with-colons", "--fingerprint" ] }, defer err, out
503 | ret = []
504 | unless err?
505 | ret = list_fingerprints out.toString('utf8')
506 | cb err, ret
507 |
508 | #----------------------------
509 |
510 | list_keys : (cb) ->
511 | await @find_keys {}, defer err, @_all_id_64s
512 | cb err, @_all_id_64s
513 |
514 | #------
515 |
516 | safe_inspect : (gargs) ->
517 | d = {}
518 | for k,v of gargs
519 | if (k is 'stdin') and gargs.secret
520 | v = ""
521 | d[k] = v
522 | util.inspect d
523 |
524 | #------
525 |
526 | gpg : (gargs, cb) ->
527 | log().debug "| Call to #{@CMD}: #{@safe_inspect gargs}"
528 | gargs.quiet = false if gargs.quiet and globals().get_debug()
529 |
530 | #
531 | # THE NUCLEAR OPTION
532 | #
533 | # The nuclear option for dealing with gpg.conf files that we
534 | # can't deal with. I would hate to do this, but there are some options,
535 | # like `--primary-keyring`, that we just can't work around if they're specified
536 | # in `gpg.conf`.
537 | #
538 | # There's an alternative to the nuclear option, which is to make a
539 | # gpg.conf that's stripped of the bad options, and to reference that
540 | # instead. That will be a fussy operation, since we don't know where
541 | # the configuration is actually stored. We can guess, but we're likely to
542 | # be wrong, **especially** on Windows. Plus, we'll have to keep it in sync
543 | # (maybe rewriting our temporary copy every time we start up).
544 | #
545 | # Discovered: `gpg --gpgconf-list`, which outputs the config file in the
546 | # top line. This is a start, but need to check how widely supported it is.
547 | #
548 | # For now, experts have to declare their willingness to go nuclear.
549 | #
550 | gargs.no_options = true if globals().get_no_options()
551 |
552 | await @run gargs, defer err, res
553 | cb err, res
554 |
555 | #------
556 |
557 | index2 : (opts, cb) ->
558 | k = if opts?.secret then '-K' else '-k'
559 | args = [ k, "--with-fingerprint", "--with-colons" ]
560 | args.push q if (q = opts.query)?
561 | await @gpg { args, quiet : true, no_options : true }, defer err, out
562 | i = w = null
563 | unless err?
564 | p = new Parser out.toString('utf8')
565 | i = p.parse()
566 | w = p.warnings()
567 | cb err, i, w
568 |
569 | #------
570 |
571 | read_uids_from_key :( {fingerprint, query}, cb) ->
572 | query = fingerprint or query
573 | opts = { query }
574 | await @index2 opts, defer err, index
575 | ret = if err? then null else index?.keys()?[0]?.userids()
576 | cb err, ret
577 |
578 | #------
579 |
580 | index : (cb) -> @index2 {}, cb
581 |
582 | #------
583 |
584 | oneshot_verify_2 : ({ring, sig, file, no_json}, cb) ->
585 | err = ret = null
586 | if file?
587 | await ring.verify_sig_on_file { sig, file }, defer err
588 | else
589 | await ring.verify_and_decrypt_sig { sig }, defer err, raw
590 | if err? then # noop
591 | else if no_json then ret = raw
592 | else
593 | await a_json_parse raw, defer err, ret
594 | cb err, ret
595 |
596 | #------
597 |
598 | oneshot_verify : ({query, single, sig, file, no_json, keyblock, secret}, cb) ->
599 | log().debug "+ oneshot verify"
600 | ring = null
601 | clean = (cb) ->
602 | log().debug "| oneshot clean"
603 | if ring?
604 | await ring.nuke defer err
605 | log().warn "Error cleaning up 1-shot ring: #{err.message}" if err?
606 | cb()
607 | cb = chain cb, clean
608 | esc = make_esc cb, "BaseKeyRing::oneshot_verify"
609 | ret = null
610 | await @make_oneshot_ring { query, single, keyblock, secret }, esc defer ring, fp
611 | await @oneshot_verify_2 { ring, sig, file, no_json }, esc defer ret
612 | log().debug "- oneshot verify -> ok! (fp=#{fp})"
613 | cb null, ret, fp
614 |
615 | ##=======================================================================
616 |
617 | exports.MasterKeyRing = class MasterKeyRing extends BaseKeyRing
618 |
619 | to_string : () -> "master keyring"
620 |
621 | mutate_args : (gargs) ->
622 | if (h = globals().get_home_dir())?
623 | gargs.args = [ "--homedir", h ].concat gargs.args
624 | log().debug "| Mutate GPG args; new args: #{gargs.args.join(' ')}"
625 |
626 | ##=======================================================================
627 |
628 | exports.make_ring = (d) ->
629 | klass = if d? then AltKeyRing else MasterKeyRing
630 | new klass d
631 |
632 | ##=======================================================================
633 |
634 | exports.reset_master_ring = reset_master_ring = () ->
635 | globals().set_master_ring new MasterKeyRing()
636 |
637 | #----------
638 |
639 | exports.master_ring = master_ring = () -> globals().master_ring()
640 |
641 | ##=======================================================================
642 |
643 | exports.load_key = (opts, cb) ->
644 | delete opts.signer if (signer = opts.signer)?
645 | key = master_ring().make_key opts
646 | await key.load defer err
647 | if not err? and signer?
648 | await key.check_is_signed signer, defer err
649 | cb err, key
650 |
651 | ##=======================================================================
652 |
653 | class RingFileBundle
654 |
655 | FILES :
656 | secring :
657 | arg : "secret-keyring"
658 | default : ""
659 | pubring :
660 | arg : "keyring"
661 | default : ""
662 | trustdb :
663 | arg : "trustdb-name"
664 | default : "016770670303010501020000532b0f0c000000000000000000000000000000000000000000000000"
665 |
666 | #-----
667 |
668 | constructor : ({@dir, pub_only, @no_default, @primary}) ->
669 | @_files = {}
670 | which = if pub_only then [ "pubring" ] else [ "pubring", "secring", "trustdb" ]
671 | for w in which
672 | @_files[w] = @get_file w
673 |
674 | #-------
675 |
676 | get_file : (which) -> path.join(@dir, [which, "gpg"].join("."))
677 |
678 | #---------
679 |
680 | mutate_args : (gargs, opts = {}) ->
681 | prepend = []
682 | prepend.push("--no-default-keyring") if @no_default or opts.no_default
683 | for w, file of @_files
684 | arg = if (w is 'pubring' and @primary) then "primary-keyring" else @FILES[w].arg
685 | prepend.push "--#{arg}", file
686 | gargs.args = prepend.concat gargs.args
687 |
688 | #---------
689 |
690 | touch : (which, f, cb) ->
691 | log().debug "+ Make/check empty #{which} #{f}"
692 | ok = true
693 | await fs.open f, "ax", 0o600, defer err, fd
694 | if not err?
695 | log().debug "| Made a new one"
696 | d = Buffer.from @FILES[which].default, "hex"
697 | await fs.write fd, d, 0, d.length, null, defer e2
698 | log().error "Write failed: #{e2.message}" if e2?
699 | else if err.code is "EEXIST"
700 | log().debug "| Found one"
701 | else
702 | log().warn "Unexpected error code from file touch #{f}: #{err.message}"
703 | ok = false
704 | if fd >= 0 and not err?
705 | await fs.close fd, defer e2
706 | log().error "Close failed: #{e2.message}" if e2?
707 | log().debug "- Made/check empty #{which} -> #{ok}"
708 | cb null
709 |
710 | #---------
711 |
712 | touch_all : (cb) ->
713 | esc = make_esc cb, "RingFileBundle::touch_all"
714 | for which, f of @_files
715 | await @touch which, f, esc defer()
716 | cb null
717 |
718 | ##=======================================================================
719 |
720 | class AltKeyRingBase extends BaseKeyRing
721 |
722 | constructor : (@dir) ->
723 | super()
724 | @_rfb = @make_ring_file_bundle()
725 |
726 | #------
727 |
728 | to_string : () -> "keyring #{@dir}"
729 |
730 | #------
731 |
732 | make_ring_file_bundle : () ->
733 | new RingFileBundle { @dir, no_default : true }
734 |
735 | #------
736 |
737 | mkfile : (n) -> path.join @dir, n
738 |
739 | #------
740 |
741 | post_make : (cb) -> @_rfb.touch_all cb
742 |
743 | #------
744 |
745 | @make : (klass, dir, cb, opts) ->
746 | opts or= {}
747 | tmp = not opts.perm
748 | type = if tmp then "temporary" else "permanent"
749 | parent = path.dirname dir
750 | nxt = path.basename dir
751 |
752 | mode = 0o700
753 | log().debug "+ Make new #{type} keychain"
754 | log().debug "| mkdir_p parent #{parent}"
755 |
756 | # If we're making a temporary directory, we can silently
757 | # make all parent directories, but we have to make the directory
758 | # itself. If we're making a permanent directory, we can
759 | # make the target directory with mkdir_p and call it quits.
760 | targ = if tmp then parent else dir
761 | await mkdir_p targ, mode, defer err, made
762 | if err?
763 | log().error "Error making keyring dir #{parent}: #{err.message}"
764 | else if made
765 | log().info "Creating #{type} keyring dir: #{targ}"
766 | else
767 | await fs.stat targ, defer err, so
768 | if err?
769 | log().error "Failed to stat directory #{targ}: #{err.message}"
770 | else if (so.mode & 0o777) isnt mode
771 | await fs.chmod targ, mode, defer err
772 | if err?
773 | log().error "Failed to change mode of #{parent} to #{mode}: #{err.message}"
774 |
775 | # If all is well and we're in tmp mode, then we need to make this
776 | # last directory, and fail with code EEXISTS if we fail to make it.
777 | if not err? and tmp
778 | dir = path.join parent, nxt
779 | await fs.mkdir dir, mode, defer err
780 | log().debug "| making directory #{dir}"
781 | if err?
782 | log().error "Failed to make dir #{dir}: #{err.message}"
783 |
784 | log().debug "- Made new #{type} keychain"
785 | tkr = if err? then null else (new klass dir)
786 | if tkr? and not err?
787 | await tkr.post_make defer err
788 | cb err, tkr
789 |
790 | #----------------------------
791 |
792 | copy_key : (k1, cb) ->
793 | esc = make_esc cb, "TmpKeyRing::copy_key"
794 | await k1.load esc defer()
795 | k2 = k1.copy_to_keyring @
796 | await k2.save esc defer()
797 | cb()
798 |
799 | #------
800 |
801 | # The GPG class will call this right before it makes a call to the shell/gpg.
802 | # Now is our chance to talk about our special keyring
803 | mutate_args : (gargs) ->
804 | @_rfb.mutate_args gargs
805 | log().debug "| Mutate GPG args; new args: #{gargs.args.join(' ')}"
806 |
807 | ##=======================================================================
808 |
809 | class TmpKeyRingBase extends AltKeyRingBase
810 |
811 | #----------------------------
812 |
813 | constructor : (dir) ->
814 | super dir
815 | @_nuked = false
816 |
817 | #----------------------------
818 |
819 | @make : (klass, cb) ->
820 | dir = path.join globals().get_tmp_keyring_dir(), base64u.encode(prng(15))
821 | AltKeyRingBase.make klass, dir, cb
822 |
823 | #----------------------------
824 |
825 | to_string : () -> "tmp keyring #{@dir}"
826 | is_temporary : () -> true
827 | tmp_dir : () -> @dir
828 |
829 | #----------------------------
830 |
831 | nuke : (cb) ->
832 | unless @_nuked
833 | log().debug "| nuking temporary keyring: #{@dir}"
834 | await fs.readdir @dir, defer err, files
835 | if err?
836 | log().error "Cannot read dir #{@dir}: #{err.message}"
837 | else
838 | for file in files
839 | fp = path.join(@dir, file)
840 | await fs.unlink fp, defer e2
841 | if e2?
842 | log().warn "Could not remove dir #{fp}: #{e2.message}"
843 | err = e2
844 | unless err?
845 | await fs.rmdir @dir, defer err
846 | if err?
847 | log().error "Cannot delete tmp keyring @dir: #{err.message}"
848 | @_nuked = true
849 | cb err
850 |
851 | ##=======================================================================
852 |
853 | exports.TmpKeyRing = class TmpKeyRing extends TmpKeyRingBase
854 |
855 | #------
856 |
857 | @make : (cb) -> TmpKeyRingBase.make TmpKeyRing, cb
858 |
859 | ##=======================================================================
860 |
861 | exports.AltKeyRing = class AltKeyRing extends AltKeyRingBase
862 |
863 | @make : (dir, cb) -> AltKeyRingBase.make AltKeyRing, dir, cb, { perm : true }
864 |
865 | ##=======================================================================
866 |
867 | exports.TmpPrimaryKeyRing = class TmpPrimaryKeyRing extends TmpKeyRingBase
868 |
869 | #------
870 |
871 | @make : (cb) -> TmpKeyRingBase.make TmpPrimaryKeyRing, cb
872 |
873 | #------
874 |
875 | make_ring_file_bundle : () ->
876 | new RingFileBundle { pub_only : true, @dir, primary : true }
877 |
878 | #------
879 |
880 | # The GPG class will call this right before it makes a call to the shell/gpg.
881 | # Now is our chance to talk about our special keyring
882 | mutate_args : (gargs) ->
883 | @_rfb.mutate_args gargs
884 | prepend = if gargs.list_keys then [ "--no-default-keyring" ]
885 | else if (h = globals().get_home_dir())
886 | [
887 | "--no-default-keyring",
888 | "--keyring", path.join(h, "pubring.gpg"),
889 | "--secret-keyring", path.join(h, "secring.gpg"),
890 | "--trustdb-name", path.join(h, "trustdb.gpg")
891 | ]
892 | else []
893 | gargs.args = prepend.concat gargs.args
894 | log().debug "| Mutate GPG args; new args: #{gargs.args.join(' ')}"
895 |
896 | ##=======================================================================
897 |
898 | exports.TmpOneShotKeyRing = class TmpOneShotKeyRing extends TmpKeyRing
899 |
900 | @make : (cb) -> TmpKeyRingBase.make TmpOneShotKeyRing, cb
901 |
902 | #---------------
903 |
904 | gpg : (gargs, cb) ->
905 | gargs.args.unshift "--no-options"
906 | super gargs, cb
907 |
908 | #---------------
909 |
910 | base_args : () -> [ "--trusted-key", @_key_id_64 ]
911 |
912 | #---------------
913 |
914 | verify_and_decrypt_sig : ({sig}, cb) ->
915 | args = @base_args().concat [ "--decrypt", "--no-auto-key-locate" ]
916 | await @gpg { args, stdin : sig, quiet : true }, defer err, out
917 | cb err, out
918 |
919 | #---------------
920 |
921 | verify_sig_on_file : ({ sig, file }, cb) ->
922 | args = @base_args().concat [ "--verify", sig, file ]
923 | await @gpg { args, quiet : true }, defer err
924 | cb err
925 |
926 | ##=======================================================================
927 |
928 | exports.QuarantinedKeyRing = class QuarantinedKeyRing extends TmpOneShotKeyRing
929 |
930 | @make : (cb) -> TmpKeyRingBase.make QuarantinedKeyRing, cb
931 |
932 | #------------------------
933 |
934 | set_fingerprint : (fp) ->
935 | @_fingerprint = fp
936 | @_key_id_64 = fingerprint_to_key_id_64 fp
937 |
938 | #------------------------
939 |
940 | # No need to make a true one-shot keyring if we have a Sequestered key,
941 | # since there's guaranteed to only have one key there.
942 | oneshot_verify : ({query, single, sig, file, no_json, keyblock}, cb) ->
943 | log().debug "+ Quarantined / oneshot verify"
944 | await @oneshot_verify_2 { ring : @, sig, file, no_json }, defer err, ret
945 | log().debug "- Quarantined / oneshot verify -> ok! (fp=#{@_fingerprint})"
946 | cb err, ret, @_fingerprint
947 |
948 | ##=======================================================================
949 |
950 |
951 |
--------------------------------------------------------------------------------
/src/main.iced:
--------------------------------------------------------------------------------
1 | modules = [
2 | require("./err")
3 | require("./gpg")
4 | require("./parse")
5 | require("./colgrep")
6 | ]
7 | for m in modules
8 | for k,v of m
9 | exports[k] = v
10 |
11 | # Export keyring stuff in a namespace
12 | exports.keyring = require('./keyring')
13 |
--------------------------------------------------------------------------------
/src/parse.iced:
--------------------------------------------------------------------------------
1 | {E} = require './err'
2 | {GPG} = require './gpg'
3 | util = require 'util'
4 |
5 | #=======================================================================
6 |
7 |
8 | strip = (x) -> if (m = x.match(/^\s*(.*)$/)) then m[1] else x
9 |
10 | class Packet
11 | constructor : ( {@type, @options } ) -> @_subfields = []
12 | add_subfield : (f) -> @_subfields.push f
13 | subfields : () -> @_subfields
14 |
15 | class Message
16 | constructor : (@_packets) ->
17 | packets : () -> @_packets
18 |
19 | #=======================================================================
20 |
21 | #
22 | # Parse GPG messages using `gpg --list-packets`
23 | #
24 | exports.Parser = class Parser
25 |
26 | constructor : (@pgp_output) ->
27 |
28 | run : () ->
29 | @preprocess()
30 | new Message @parse_packets()
31 |
32 | preprocess : () -> @_lines = (line for line in @pgp_output.split(/\r?\n/) when line.match /\S/)
33 | parse_packets : () -> (@parse_packet() until @eof())
34 | peek : () -> @_lines[0]
35 | get : () -> @_lines.shift()
36 | eof : () -> @_lines.length is 0
37 |
38 | parse_packet : () ->
39 | rxx = /^:([a-zA-Z0-9_ -]+) packet:( (.*))?$/
40 | first = @get()
41 | unless (m = first.match rxx)
42 | throw new E.ParseError "expected ':literal data packet:' style header; got #{first}"
43 | packet = new Packet { type : m[1], options : m[3] }
44 | until (@eof() or @peek()[0] is ':')
45 | packet.add_subfield strip(@get())
46 | return packet
47 |
48 | #=======================================================================
49 |
50 | exports.parse = parse = ({gpg, message }, cb) ->
51 | gpg or= new GPG
52 | out = null
53 | await gpg.run { args : [ "--list-packets"], stdin : message }, defer err, buf
54 | unless err?
55 | try
56 | out = (new Parser buf.toString('utf8')).run()
57 | catch e
58 | err = e
59 | cb err, out
60 |
61 | #=======================================================================
62 |
63 |
--------------------------------------------------------------------------------
/test/files/colgrep.iced:
--------------------------------------------------------------------------------
1 |
2 | {colgrep} = require '../../lib/main'
3 |
4 | exports.test1 = (T, cb) ->
5 | input = """
6 | pub:u:2048:1:CC19461E16CD52C8:1388413669:1703773669::u:::scESC:
7 | uid:u::::1388413669::4A1C93DDE1779B6BE90393F4394AD983EC785808::Brown Hat I (pw is 'a') :
8 | sub:u:2048:1:079F6793014A5F79:1388413669:1703773669:::::e:
9 | pub:f:1024:1:5308C23E96D307BE:1387294526:1702654526::-:::escaESCA:
10 | uid:f::::1387294526::9A7B91D6B62DC24454C408CB4D98210A31F4235F::keybase.io/taco1 (v0.0.1) :
11 | sub:f:1024:1:5F8664C8B707AD86:1387294526:1418830526:::::esa:
12 | pub:-:1024:1:361D07D32CDF514E:1389274486:1704634486::-:::escaESCA:
13 | uid:-::::1389274486::039EE0C3AD2BCAFEA038A768F4288963FDD7C1E6::keybase.io/taco19 (v0.0.1) :
14 | sub:-:1024:1:6F61BAE56CA1B67A:1389274486:1420810486:::::esa:
15 | """
16 | res = colgrep {
17 | patterns : {
18 | 0 : /pub|sub/
19 | 4 : /A.*7/
20 | },
21 | buffer : (Buffer.from input, 'utf8')
22 | separator : /:/
23 | }
24 | T.equal res[0]?[4], '079F6793014A5F79', 'first row found'
25 | T.equal res[1]?[4], '6F61BAE56CA1B67A', '2nd row found'
26 | cb()
27 |
--------------------------------------------------------------------------------
/test/files/error.iced:
--------------------------------------------------------------------------------
1 |
2 | {E,GPG} = require '../../lib/main'
3 |
4 | exports.test_error_1 = (T,cb) ->
5 | gpg = new GPG
6 | await gpg.run { args : [ "-k", "-e" ], quiet : 'true' }, defer err
7 | T.assert err?, "got an error back"
8 | T.assert (err instanceof E.GpgError), "of the right instance"
9 | T.equal err.stderr.toString('utf8'), "gpg: conflicting commands\n", "got an error in our Error"
10 | cb()
--------------------------------------------------------------------------------
/test/files/gpg.iced:
--------------------------------------------------------------------------------
1 |
2 | {pinentry_init,get_gpg_cmd,find_and_set_cmd,colgrep,GPG,set_gpg_cmd} = require '../../lib/main'
3 |
4 | exports.pinentry_init = (T,cb) ->
5 | await pinentry_init defer err, out
6 | T.no_error err
7 | cb()
8 |
9 | exports.test_assert_no_collision = (T,cb) ->
10 | obj = new GPG()
11 | await obj.run { args : [ "-k", "--with-colons" ] }, defer err, out
12 | T.no_error err
13 | out = colgrep {
14 | patterns : {
15 | 0 : /[ps]ub/
16 | },
17 | buffer : out,
18 | separator : /:/
19 | }
20 | T.assert (out.length > 0), "need at least 1 key to make this work"
21 | key = out[0][4]
22 | await obj.assert_no_collision key, defer err, n
23 | T.no_error err
24 | T.equal n, 1, "we found exactly 1 key"
25 | cb()
26 |
27 | exports.test_success = (T,cb) ->
28 | x = new GPG()
29 | await x.test defer err
30 | T.no_error err
31 | cb()
32 |
33 | exports.test_failure = (T,cb) ->
34 | x = new GPG { cmd : "no_way_jose" }
35 | await x.test defer err
36 | T.assert err?, "failed to launch non-existent proc"
37 | cb()
38 |
39 | exports.test_failure_2 = (T,cb) ->
40 | set_gpg_cmd "blah_blah"
41 | x = new GPG { }
42 | await x.test defer err
43 | T.assert err?, "failed to launch non-existent proc"
44 | cb()
45 |
46 | exports.test_success_2 = (T,cb) ->
47 | set_gpg_cmd "gpg"
48 | x = new GPG {}
49 | await x.test defer err
50 | T.no_error err, "and reset properly to standard gpg"
51 | cb()
52 |
53 |
54 | # Test will only work if you have both gpg and gpg2 installed
55 | exports.test_find = (T,cb) ->
56 | await find_and_set_cmd null, defer err, version, cmd
57 | T.no_error err
58 | T.assert version?, "version came back"
59 | T.equal cmd, "gpg2", "should find gpg2 by default"
60 | T.equal get_gpg_cmd(), "gpg2", "should update global preferences"
61 | await find_and_set_cmd "gpg", defer err, version, cmd
62 | T.no_error err
63 | T.assert version?, "version came back"
64 | T.equal cmd, "gpg", "should find gpg if we asked"
65 | T.equal get_gpg_cmd(), "gpg", "should update global preferences"
66 | cb()
67 |
--------------------------------------------------------------------------------
/test/files/index.iced:
--------------------------------------------------------------------------------
1 |
2 | {Parser,parse} = require '../../lib/index'
3 | {TmpKeyRing} = require '../../lib/keyring'
4 |
5 | #======================================================================
6 |
7 | gpg_output = """
8 | tru::1:1392463756:1439900531:3:1:5
9 | pub:u:2048:17:76D78F0500D026C4:1282220531:1439900531::u:::scESC:
10 | fpr:::::::::85E38F69046B44C1EC9FB07B76D78F0500D026C4:
11 | uid:u::::1344775710::CAACC8CE9116A0BE42E58C61602F127B194EF5A5::GPGTools Team :
12 | uid:u::::1344775710::8CACAFAD028BE38151D2361F9CD79CC81B4153B2::GPGTools Project Team (Official OpenPGP Key) :
13 | uid:u::::1282220531::03B2DCE7652DBBB93DA77FFC4328F122656E20DD::GPGMail Project Team (Official OpenPGP Key) :
14 | uat:u::::1321476238::076E59FC200B10E38AEEA745AB6547AEE99FB9EE::1 5890:
15 | sub:u:2048:16:07EAE49ADBCBE671:1282220531:1439900531:::::e:
16 | pub:e:2048:1:78DCF9A45638F6F1:1324057408:1381019002::u:::esca:
17 | fpr:::::::::5CB9294EDE703C2ED76B649478DCF9A45638F6F1:
18 | uid:e::::1380932602::BAE797EBBD23C9257BC5C5274AE498263601FFFF::Max Krohn :
19 | sub:e:2048:1:9B54F0B9FC576209:1324057408:1450287808:::::esa:
20 | pub:e:2048:1:4437D388669EF5CF:1342348430:1381015753::u:::esca:
21 | fpr:::::::::142B2FB6EC949AB9AAC6E2904437D388669EF5CF:
22 | uid:e::::1380929353::759D5C7C38AD60551D46D2E6F34BA03640FE4379::Maxwell Krohn :
23 | sub:e:2048:1:86AEC52166C49EC9:1342348430:1468578830:::::esa:
24 | pub:u:4096:1:63847B4B83930F0C:1380929487:1507159887::u:::escaESCA:
25 | fpr:::::::::4475293306243408FA5958DC63847B4B83930F0C:
26 | uid:u::::1386987949::759D5C7C38AD60551D46D2E6F34BA03640FE4379::Maxwell Krohn :
27 | uid:u::::1380929487::14BC0C35326061518657E0B8F71A23E0CA537034::Max Krohn :
28 | sub:u:4096:1:2FE01C454348DA39:1380929487:1507159887:::::esa:
29 | pub:u:4096:1:FBC07D6A97016CB3:1381159886:1507390286::u:::escaESCA:
30 | fpr:::::::::94AA3A5BDBD40EA549CABAF9FBC07D6A97016CB3:
31 | uid:u::::1381159886::CF5361B3D400B377CED49FD557B744E93FA344D4::Chris Coyne :
32 | sub:u:4096:1:D224413B1CFA6490:1381159886:1507390286:::::esa:
33 | pub:-:4096:1:EBF01804BCF05F6B:1346326188:::-:::escaESCA:
34 | fpr:::::::::428DF5D63EF07494BB455AC0EBF01804BCF05F6B:
35 | uid:-::::1360530305::ED4AFBC98FD3B49AE8CA7733A27BE82FFFB5E53F::Filippo Valsorda :
36 | uid:-::::1360528876::788372CBC7DDF24564AEA7138ABB09A648499E32::Filippo Valsorda :
37 | sub:e:2048:1:50223425F149AA25:1360529005:1392065005:::::s:
38 | sub:e:2048:1:3D1C752C0D83D9EC:1360529191:1392065191:::::e:
39 | pub:e:4096:1:56A4286A3DD61E72:1232175770:1263711770::-:::sc:
40 | fpr:::::::::ADBDA177656FD5228E34C45656A4286A3DD61E72:
41 | uid:e::::1232175770::913D07A9BE150E31934E0BDD30E0E72BAA00972D::Tarsnap source code signing key (Colin Percival) :
42 | pub:u:4096:1:47484E50656D16C7:1384876967:1511107367::u:::scESC:
43 | fpr:::::::::222B85B0F90BE2D24CFEB93F47484E50656D16C7:
44 | uid:u::::1384876967::5379B9706C5D468C86A572B07E28EBDB26BE0E97::Keybase.io Code Signing (v1) :
45 | sub:u:4096:1:5929664098F03378:1384876967:1511107367:::::e:
46 | pub:-:4096:1:C9A2613970FB9C1A:1387730124:1703090124::-:::escaESCA:
47 | fpr:::::::::F6E88E94B87E7F4739516D3CC9A2613970FB9C1A:
48 | uid:-::::1387730124::55897318ED31ED923F78F39309C6A7B30712C5D3::keybase.io/taco3 (v0.0.1) :
49 | sub:-:2048:1:22A07D398AF7E83D:1387730124:1419266124:::::esa:
50 | pub:-:4096:1:6FB0BE75C0B301AC:1388161792:1703521792::-:::escaESCA:
51 | fpr:::::::::7BC8BF5CC032C179C2C82A486FB0BE75C0B301AC:
52 | uid:-::::1388161792::FFFDF09966976366BE820498EEA957D65FE9C305::keybase.io/noob (v0.0.1) :
53 | sub:-:2048:1:E35826CED986F0AC:1388161792:1419697792:::::esa:
54 | pub:-:1024:1:C149654BD8A2C080:1389446345:1704806345::-:::escaESCA:
55 | fpr:::::::::DDF4EDB21FC5F94F9EDFF2C2C149654BD8A2C080:
56 | uid:-::::1389446345::E2B10048BC87972EF6D9CC80A9C59945CF521044::keybase.io/noob6 (v0.0.1) :
57 | sub:-:1024:1:DA18DF4A4C231AF1:1389446345:1420982345:::::esa:
58 | pub:f:4096:1:199A25A57F9E8BFA:1390244586:1705604586::-:::scESC:
59 | fpr:::::::::50356E55ADBC1310C156B7F5199A25A57F9E8BFA:
60 | uid:f::::1390244586::5D4D8FC2FE2BA87FABB596E5E02E3DA9416F9F1B::Keybase.io Index Signing (v1) :
61 | sub:f:4096:1:2A934F99974F25D4:1390244586:1705604586:::::e:
62 | pub:u:4096:1:6052B2AD31A6631C:1391639420:1706999420::u:::escaESCA:
63 | fpr:::::::::8EFBE2E4DD56B35273634E8F6052B2AD31A6631C:
64 | uid:u::::1391639420::0E83836893BE1130CC0CCF718A26FA19BCDC5866::keybase.io/max (v0.0.1) :
65 | sub:u:2048:1:980A3F0D01FE04DF:1391639420:1423175420:::::esa:
66 | pub:-:4096:1:5B094948B11151F2:1392237184:1707597184::-:::escaESCA:
67 | fpr:::::::::E0600318C622F735D82EDF3D5B094948B11151F2:
68 | uid:-::::1392237184::5D9DB4E2334A1681D7F40E30DFAE61319BAA78A5::keybase.io/capndesign (v0.0.1) :
69 | sub:-:2048:1:4BABF9BC0ABA323B:1392237184:1423773184:::::esa:
70 | pub:u:1024:1:565DCCFB57308A88:1392463661:1518694061::u:::scESC:
71 | fpr:::::::::EAF36B487D10F811A34EDC40565DCCFB57308A88:
72 | uid:u::::1392463661::DF049F3B0441CA5D41B13407F3A6AFEF9E493238::Herbert Kitchener :
73 | uat:u::::1392463749::4EA48152D3CC8001632378E58194F0EDDBB267D8::1 10942:
74 | sub:u:1024:1:2A0CB1102B533C91:1392463661:1518694061:::::e:
75 | """
76 |
77 | #======================================================================
78 |
79 | exports.parse_1 = (T,cb) ->
80 | i = parse gpg_output
81 | T.assert i?, "index came back"
82 | q = "themax@gmail.com"
83 | results = i.lookup().email.get(q)
84 | T.equal results.length, 2, "the right number of records"
85 | for r,i in results
86 | found = false
87 | for u in r.userids()
88 | found = true if u.email is q
89 | T.waypoint("found #{q} in result #{i}")
90 | T.assert found, "found for result #{i}"
91 | cb()
92 |
93 | #======================================================================
94 |
95 | exports.parse_2 = (T,cb) ->
96 | p = new Parser gpg_output
97 | i = p.parse()
98 | T.assert i?, "index came back"
99 | T.equal p.warnings().warnings().length, 0, "no warnings"
100 | p = new Parser ("badstuff\n" + gpg_output + "\nbad:line\n")
101 | i = p.parse()
102 | T.assert i?, "index came back"
103 | w = p.warnings().warnings()
104 | T.equal w.length, 2, "found 2 warnings"
105 | T.equal w[0].indexOf("1: "), 0, "Found '1: ' error prefix"
106 | T.equal w[1].indexOf("69: "), 0, "Found '87: ' error prefix"
107 | cb()
108 |
109 | #======================================================================
110 |
111 | gpg_with_fingerprint2_output = """
112 | tru::1:1393721309:1707777704:3:1:5
113 | pub:-:4096:1:6052B2AD31A6631C:2014-02-05:2024-02-03::-:keybase.io/max (v0.0.1) ::escaESCA:
114 | fpr:::::::::8EFBE2E4DD56B35273634E8F6052B2AD31A6631C:
115 | sub:-:2048:1:980A3F0D01FE04DF:2014-02-05:2015-02-05:::::esa:
116 | fpr:::::::::4AF88842F72A59565C669BDE980A3F0D01FE04DF:
117 | pub:-:4096:1:BAD2382DE379088C:2014-03-01:2024-02-27::-:keybase.io/max2 ::escaESCA:
118 | fpr:::::::::741AE71745FE2A9FCE7FB5BCBAD2382DE379088C:
119 | sub:-:4096:1:276556C8A2529039:2014-03-01:2024-02-27:::::esa:
120 | fpr:::::::::D505C8C831C3DDF74813DDFF276556C8A2529039:
121 | pub:-:4096:1:6ADC353FA59EAC16:2014-02-28:2024-02-26::-:keybase.io/max1 (v0.0.1) ::escaESCA:
122 | fpr:::::::::FA6DFC44C9426A3DEF8B72336ADC353FA59EAC16:
123 | sub:-:2048:1:CF0E4A8E88CBB038:2014-02-28:2015-02-28:::::esa:
124 | fpr:::::::::67D9D22291C4D64C0E038C32CF0E4A8E88CBB038:
125 | pub:u:4096:1:C2A863B7D67F29AE:2014-03-02:2024-02-28::u:keybase.io/max2 ::escaESCA:
126 | fpr:::::::::A950C514D2EA4A9FB623C97FC2A863B7D67F29AE:
127 | sub:u:4096:1:527C4EF645328914:2014-03-02:2024-02-28:::::esa:
128 | fpr:::::::::AF8A6C125F1A20D89F0BC0A3527C4EF645328914:
129 | pub:-:4096:1:CB963D072C21251A:2014-03-02:2024-02-28::-:keybase.io/max1 (v0.0.1) ::escaESCA:
130 | fpr:::::::::78E4953F9FF6661C60607AC8CB963D072C21251A:
131 | sub:-:2048:1:19F02EE567F65B75:2014-03-02:2015-03-02:::::esa:
132 | fpr:::::::::22B68606B648845584EABDFE19F02EE567F65B75:
133 | pub:-:4096:1:63847B4B83930F0C:2013-10-04:2017-10-04::-:Maxwell Krohn ::escaESCA:
134 | fpr:::::::::4475293306243408FA5958DC63847B4B83930F0C:
135 | uid:-::::2013-10-04::14BC0C35326061518657E0B8F71A23E0CA537034::Max Krohn :
136 | sub:-:4096:1:2FE01C454348DA39:2013-10-04:2017-10-04:::::esa:
137 | fpr:::::::::C4EE7BCBCE2F0953DCF9E8902FE01C454348DA39:
138 | """
139 |
140 | #======================================================================
141 |
142 | exports.parse_3 = (T, cb) ->
143 | p = new Parser gpg_with_fingerprint2_output
144 | i = p.parse()
145 | T.assert i?, "index came back"
146 | T.equal i.keys().length, 6, "got back 6 keys"
147 | v = i.lookup().email.get('max@keybase.io')
148 | T.equal v.length, 1, "got only 1 key back"
149 | T.equal v[0].fingerprint(), '8EFBE2E4DD56B35273634E8F6052B2AD31A6631C', "right primary fingerprint"
150 | T.equal v[0].key_id_64(), '6052B2AD31A6631C', "right primary key id 64"
151 | T.equal v[0].subkeys().length, 1, "got only one subkey"
152 | T.equal v[0].subkeys()[0].fingerprint(), '4AF88842F72A59565C669BDE980A3F0D01FE04DF', "right subkey fingerprint"
153 | T.equal v[0].subkeys()[0].key_id_64(), '980A3F0D01FE04DF', "right subkey fingerprint"
154 | cb()
155 |
156 | #======================================================================
157 |
158 | gpg_with_fingerprint_K_output = """
159 | sec::1024:17:2FC3671F312AA0FD:1400185635::::::ES:::#:
160 | fpr:::::::::FC60FD0920968905002D0B442FC3671F312AA0FD:
161 | uid:::::1400185635::A268A2439BB76EAAD8FF5C1D049A57989CF46D1C::Max 94 :
162 | ssb::2048:1:3E41DB0C88907077:1400185645::::::s::::
163 | fpr:::::::::02ACD95A6D8C84FBC3216D923E41DB0C88907077:
164 | ssb::2048:1:29F867D20E6CF5B6:1400185659::::::e::::
165 | fpr:::::::::B7C68A881CD1C60A993248B729F867D20E6CF5B6:
166 | """
167 |
168 | #======================================================================
169 |
170 | exports.parse_4 = (T, cb) ->
171 | p = new Parser gpg_with_fingerprint_K_output
172 | i = p.parse()
173 | T.assert i?, "index came back"
174 | T.equal i.keys().length, 1, "got back 1 key"
175 | v = i.lookup().email.get('themax+942@gmail.com')
176 | T.equal v.length, 1, "got only 1 key back"
177 | T.equal v[0].fingerprint(), 'FC60FD0920968905002D0B442FC3671F312AA0FD', "right primary fingerprint"
178 | T.equal v[0].key_id_64(), '2FC3671F312AA0FD', "right primary key id 64"
179 | T.equal v[0].subkeys().length, 2, "got two subkeys"
180 | T.equal v[0].subkeys()[0].fingerprint(), '02ACD95A6D8C84FBC3216D923E41DB0C88907077', "right subkey fingerprint"
181 | T.equal v[0].subkeys()[0].key_id_64(), '3E41DB0C88907077', "right subkey fingerprint"
182 | T.equal v[0].subkeys()[1].fingerprint(), 'B7C68A881CD1C60A993248B729F867D20E6CF5B6', "right subkey fingerprint"
183 | T.equal v[0].subkeys()[1].key_id_64(), '29F867D20E6CF5B6', "right subkey fingerprint"
184 | cb()
185 |
186 | #======================================================================
187 |
--------------------------------------------------------------------------------
/test/files/keyring.iced:
--------------------------------------------------------------------------------
1 |
2 | keyring = require '../../lib/keyring'
3 |
4 | #-----------------------------------
5 |
6 | class Log extends keyring.Log
7 | debug : (x) ->
8 |
9 | #-----------------------------------
10 |
11 | ring2 = ring = null
12 | key = null
13 | key_data = """
14 | -----BEGIN PGP PUBLIC KEY BLOCK-----
15 | Version: GnuPG v1.4.14 (GNU/Linux)
16 |
17 | mI0EUqpp2QEEANFByr3uPGsG5DqmV3kPLsTEmew5d8NcD3SqASas342LB5sDE0D6
18 | 0fTDvjLYAiCTgVlZrSIx+SeeskygKH/AwnTCBK04V0HgpR0tyw+dGIV5ujFIo236
19 | O8XvIqaVoR1/zizy8fOSaFqr8rPQf3JYWxQn8IMLUS+ricOUZS/YSgNVABEBAAG0
20 | M0dhdmlyaWxvIFByaW5jaXAgKHB3IGlzICdhJykgPGdtYW5AdGhlYmxhY2toYW5k
21 | LmlvPoi+BBMBAgAoBQJSqmnZAhsDBQkSzAMABgsJCAcDAgYVCAIJCgsEFgIDAQIe
22 | AQIXgAAKCRDuXBLqbhXbknHWBACGwlrWuJyAznzZ++EGpvhVZBdgcGlU3CK2YOHC
23 | M9ijVndeXjAtAgUgW1RPjRCopjmi5QKm+YN1WcAdf6I+mnr/tdYhPYnRE+dNsEB7
24 | AWGsiwZOxQbwtCOIR+5AU7pzIoIUW1GsqQK3TbiuSRYI5XG6UdcV5SzQI96aKGvk
25 | S6O6uLiNBFKqadkBBADW31A7htB6sJ71zwel5yyX8NT5fD7t9xH/XA2dwyJFOKzj
26 | R+h5q1KueTPUzrV781tQW+RbHOsFEG99gm3KxuyxFkenXb1sXLMFdAzLvBuHqAjQ
27 | X9pJiMTCAK7ol6Ddtb/4cOg8c6UI/go4DU+/Aja2uYxuqOWzwrantCaIamVEywAR
28 | AQABiKUEGAECAA8FAlKqadkCGwwFCRLMAwAACgkQ7lwS6m4V25IQqAQAg4X+exq1
29 | +wJ3brILP8Izi74sBmA0QNnUWk1KdVA92k/k7qA/WNNobSZvW502CNHz/3SQRFKU
30 | nUCByGMaH0uhI6Fr1J+pjDgP3ZelZg0Kw1kWvkvn+X6aushU3NHtyZbybjcBYV/t
31 | 6m5rzEEXCUsYrFvtAjG1/bMDLT0t1AA25jc=
32 | =59sB
33 | -----END PGP PUBLIC KEY BLOCK-----
34 | """
35 | fingerprint = "1D1A20E57C763DD42258FBC5EE5C12EA6E15DB92"
36 |
37 | payload = "I hereby approve of the Archduke's assassination. Please spare his wife.\n"
38 | sig = """
39 | -----BEGIN PGP MESSAGE-----
40 | Version: GnuPG v1.4.14 (GNU/Linux)
41 |
42 | owGbwMvMwMT4LkboVZ7o7UmMpwOSGIJu/27zVMhILUpNqlRILCgoyi9LVchPUyjJ
43 | SFVwLErOSCnNTlUvVkgsLgaizLzEksz8PD0FhYCc1MTiVIXigsSiVIWMzGKF8sy0
44 | VD2ujjksDIxMDGysTCBzGbg4BWCWrVNjYWg1Py9w8X/oMuuysk7JcilXkWqjy9uX
45 | N8bOfbp+ZZK7rYGMD++edRt9Mk5ITp+2cPcunVXv2FmCO6d6SD3lnOybvcXytJFt
46 | S+fz1cqTPdi3dT47XXj97IWY65u1pO9HBUZmy0/YzihX4Pz/ZIO7hnfb1N4l7Fw/
47 | Hz30FTkcaHWq7oPoHAWeYwA=
48 | =2ENa
49 | -----END PGP MESSAGE-----
50 | """
51 |
52 | #-----------------
53 |
54 | kaiser2 =
55 | key_data : """
56 | -----BEGIN PGP PUBLIC KEY BLOCK-----
57 | Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
58 | Comment: GPGTools - https://gpgtools.org
59 |
60 | mQENBFLf0DYBCADGz/jWmSDY8c4yVorLgDXK1GpHmqmGOaacBjdSC0Os0+oBcvI7
61 | o7rVZkkOeHoLGfr4HaQ6iXF61PxMjRpvUmDMrznrYGnOsSiiY0S6IFmAoEnu7BqI
62 | 2ZPQEwqxV4o9iQ6ttffh0LC/5IX3+0sXt6uWebAyE0fW3Rw1drSaElUdzXRu7/nw
63 | e75oLhNSVguLFMhhs6VvUglcYRsZJN+hNOW0oVOIBWHDCtI713U/wFepaOov0g48
64 | Ysj2gFLnhUMGPgb+yTKeDLvlQQCZoIXBWWTy7sM/LU0xsegP0Wpsv+aj7fNcoxYp
65 | tuqQwPOzu35B7J4++ECmQ0qoVND9j5iChA2xABEBAAG0IkthaXNlciBXaWxoZWxt
66 | IDxrYWlzZXIyQHlhaG9vLmNvbT6JAT0EEwEKACcFAlLf0DYCGwMFCRLMAwAFCwkI
67 | BwMFFQoJCAsFFgIDAQACHgECF4AACgkQY+EQEyiPLUiCcQf/REXdwDfJRHc7DpWJ
68 | M/o+NDke4d60gh3wtNRUWlsbAF/Xc1aZjEJt0xRIx3QJ8P5+FYfMsk2/05UfXmrg
69 | KE69AEP2x3FcbnkYeSG0jwbDi5h7such17SDxV9M/s4iHjJKBglyDxYltG2xZ8Xu
70 | NNTi2VkvvulrRcwonQr/hPDibMKIgY7Xrxw3nZK/pOOXaidcIvuoGpOd9w3UniGc
71 | zOzSi3CqTQrpf+5/p0rZE2tdJdNTm2MUww8FiUzpzdUfMAunVSpK1WazpWXkJ9sS
72 | 4H6dE+GUWh71f4j69NNfzOG4YWQ6syjJ12BtlLRNl403LvPubhVaKr0gHF5wEeI0
73 | 4h0YR7kBDQRS39A2AQgA6Vfl/9NBltJOxQ921rJJTqsjxW/chIX1HGYEYWRrJrfD
74 | iEoC1jYDOKmP6q6PYREeyhB1G5uER2FQnjtxY8Wo+BpwsJ+s/stgRZoHUM2AsmPb
75 | rnEt8J781trSjbTuySgRkqsQAQizhrsAq0jnpOCmAPVbsFvC4oV9kjiXDOet2j9j
76 | tfZ5FnfESqQ0tmrdIXvKaa3+jE5hnRvyhBrwyoYny6SBw4eqogUjQnUa1yo4X0Si
77 | dPmNDA6DdIFG6+OxR3emRNxeuYRq3oHJLAalGlhMQCn8QK1RyoyRqDcekMHB0hYV
78 | uNgxgta11hIdipBohrJIeKVcGKXt29XSnS0iWdm/DQARAQABiQElBBgBCgAPBQJS
79 | 39A2AhsMBQkSzAMAAAoJEGPhEBMojy1I1t8H/3hXg/3WwN33iY1bodU8oXYVBbKG
80 | pjs/A/fP/H3+3MqG6z/sspUfXluS7baNRmg5HB300vMGqPRJ5EU5/anOu/EJxj1A
81 | NJpSiJnXyjwVx7EviMgLPlZC0HTYNPsiXZLe7p/WAWHy5HRH9iAgu0IOPon1MniV
82 | 9lgJHsOS+zF+ostVB6PFglEJt4y7ySeFuxpRTi0ulYyO/LHW7nJZkl6xzpvykJws
83 | 3it8W3ecYAodkySzgLN8zqS8nlqlsJgO/NYIQd3c67MKA+92A75VlCMJ25SrsaVm
84 | Enqh2naqYfmkmhWk5KInf02pSAwD5I/roTYO9kO1QjLrVj1d058K2/T2Z0o=
85 | =U857
86 | -----END PGP PUBLIC KEY BLOCK-----
87 | """
88 | username : "kaiser2@yahoo.com"
89 | fingerprint : "A03CC843D8425771B59F731063E11013288F2D48"
90 | payload_json : { ww : 1 }
91 | payload : '{ "ww" : 1 }\n'
92 | sig : """-----BEGIN PGP MESSAGE-----
93 | Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
94 | Comment: GPGTools - https://gpgtools.org
95 |
96 | owEBQwG8/pANAwAKAWPhEBMojy1IAcsTYgBS39KUeyAid3ciIDogMSB9CokBHAQA
97 | AQoABgUCUt/SlAAKCRBj4RATKI8tSN+tB/90cxDDxC0PjoPqbO2ZrbI1q2FGyZI3
98 | Ayukt+u/cTadECcigJzE05ymKevKCVJFHASEp4SMn9nW4QSD5fTRcqo6QBfWImQi
99 | UYbirBvhejAARusJmLKtpmosxxsiEYQ1bcFJjx2+UQLr40uw5RHXfgP8CuUqadrw
100 | Wm+wqLwUwXxbrYb5FCZ8nziEUwOl2rpqV1NIj59D3BZps43Q5QCCTRZF5+eJJyg+
101 | AhyYGythrOMbYKWmRRGhIdy3QU34IHGxNh3o2bz6YBiM/JD8CY0M0HT33xU93LvB
102 | 7UowhdY7p9M8R0Ql21T4+5AOxPxHQIypRKOl5oJPvZg8avtDT8sc5fRw
103 | =Uy2n
104 | -----END PGP MESSAGE-----
105 | """
106 |
107 | #-----------------
108 |
109 | exports.init = (T, cb) ->
110 | keyring.init {
111 | get_preserve_tmp_keyring : () -> false
112 | get_tmp_keyring_dir : () -> "."
113 | log : new Log()
114 | }
115 | cb()
116 |
117 | #-----------------
118 |
119 | exports.make_ring = (T,cb) ->
120 | await keyring.TmpKeyRing.make defer err, tmp
121 | T.no_error err
122 | T.assert tmp, "keyring came back"
123 | ring = tmp
124 | cb()
125 |
126 | #-----------------
127 |
128 | exports.test_import = (T,cb) ->
129 | key = ring.make_key {
130 | key_data,
131 | fingerprint,
132 | username : "gavrilo"
133 | }
134 | await key.save defer err
135 | T.no_error err
136 | await key.load defer err
137 | T.no_error err
138 | cb()
139 |
140 | #-----------------
141 |
142 | exports.test_verify = (T,cb) ->
143 | await key.verify_sig { sig, payload, which : "msg" }, defer err
144 | T.no_error err
145 | cb()
146 |
147 | #-----------------
148 |
149 | exports.test_read_uids = (T, cb) ->
150 | await ring.read_uids_from_key { fingerprint }, defer err, uids
151 | T.no_error err
152 | T.equal uids.length, 1, "the right number of UIDs"
153 | # Whoops, there was as typo when I made this key!
154 | T.equal uids[0].username, "Gavirilo Princip" , "the right username"
155 | cb()
156 |
157 | #-----------------
158 |
159 | exports.test_copy = (T,cb) ->
160 | await keyring.TmpKeyRing.make defer err, ring2
161 | T.no_error err
162 | T.assert ring2, "keyring2 was made"
163 | await ring2.read_uids_from_key { fingerprint }, defer err, uids
164 | T.assert err, "ring2 should be empty"
165 | key2 = key.copy_to_keyring ring2
166 | await key2.save defer err
167 | T.no_error err
168 | await key2.load defer err
169 | T.no_error
170 | await key2.verify_sig { sig, payload, which : "key2" }, defer err
171 | T.no_error err
172 | await ring2.nuke defer err
173 | T.no_error err
174 | cb()
175 |
176 | #-----------------
177 |
178 | exports.test_find = (T, cb) ->
179 | await ring.find_keys { query : "gman@" }, defer err, id64s
180 | T.no_error err
181 | T.equal id64s, [ fingerprint[-16...] ], "got back the 1 and only right key"
182 | cb()
183 |
184 | #-----------------
185 |
186 | exports.test_list = (T,cb) ->
187 | await ring.list_keys defer err, id64s
188 | T.no_error err
189 | T.equal id64s, [ fingerprint[-16...] ], "got back the 1 and only right key"
190 | cb()
191 |
192 | #-----------------
193 |
194 | exports.test_one_shot = (T,cb) ->
195 | await ring.make_oneshot_ring { query : fingerprint, single : true }, defer err, r1
196 | T.no_error err
197 | T.assert r1, "A ring came back"
198 | await r1.nuke defer err
199 | T.no_error err
200 | cb()
201 |
202 | #-----------------
203 |
204 | exports.test_oneshot_verify = (T,cb) ->
205 | key = ring.make_key kaiser2
206 | await key.save defer err
207 | T.no_error err
208 | await ring.oneshot_verify { query : kaiser2.username, single : true, sig : kaiser2.sig }, defer err, json
209 | T.no_error err
210 | T.equal kaiser2.payload_json, json, "JSON payload checked out"
211 | cb()
212 |
213 | #-----------------
214 |
215 | exports.test_verify_sig = (T,cb) ->
216 | await key.verify_sig { which : "something", payload : kaiser2.payload, sig : kaiser2.sig }, defer err
217 | T.no_error err
218 | cb()
219 |
220 | #-----------------
221 |
222 | exports.test_import_by_username = (T,cb) ->
223 | key = ring.make_key {username : ""}
224 | await key.load defer err
225 | T.no_error err
226 | T.equal key.uid().username, 'Gavirilo Princip', "username came back correctly after load"
227 | cb()
228 |
229 | #-----------------
230 |
231 | exports.test_import_by_username_with_space_and_control_chars = (T,cb) ->
232 | if process.platform is 'win32'
233 | T.waypoint "skipped on windows :("
234 | else
235 | key = ring.make_key keybase_v1_index
236 | await key.save defer err
237 | T.no_error err
238 | key = ring.make_key {username : "(v1) "}
239 | await key.load defer err
240 | T.no_error err
241 | T.equal key.uid().username, 'Keybase.io Index Signing', "username came back correctly after load"
242 | cb()
243 |
244 | #-----------------
245 |
246 | keybase_v1_index =
247 | key_data : """
248 | -----BEGIN PGP PUBLIC KEY BLOCK-----
249 | Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
250 | Comment: GPGTools - http://gpgtools.org
251 |
252 | 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
310 | =kK8c
311 | -----END PGP PUBLIC KEY BLOCK-----
312 | """
313 |
314 | #-----------------
315 |
316 | exports.nuke_ring = (T,cb) ->
317 | await ring.nuke defer err
318 | T.no_error err
319 | cb()
320 |
321 | #==================================================================
322 |
323 | keyring_raw = """
324 | -----BEGIN PGP PUBLIC KEY BLOCK-----
325 | Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
326 | Comment: GPGTools - http://gpgtools.org
327 |
328 | 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
683 | A+k/SlaiV1ZqK5YbGFS+
684 | =7jBz
685 | -----END PGP PUBLIC KEY BLOCK-----
686 | """
687 |
688 | exports.index_keyring = (T,cb) ->
689 | await keyring.TmpKeyRing.make defer err, ring
690 | T.no_error err, "make tmp key ring"
691 | await ring.gpg { args : [ "--no-options", "--import"], stdin : keyring_raw, quiet : true }, defer err
692 | T.no_error err, "import worked ok"
693 | await ring.index defer err, index
694 | fp = "E0600318C622F735D82EDF3D5B094948B11151F2".toLowerCase()
695 | keys = index.lookup().fingerprint.get(fp)
696 | T.assert keys?, "key came back for our fingerprint #{fp}"
697 | T.equal keys.length, 1, "only one key came back"
698 | T.equal keys[0].userids().length, 1, "only 1 userid"
699 | T.equal keys[0].userids()[0].username, "keybase.io/capndesign", "and it was the capn"
700 | T.no_error err, "indexing worked ok"
701 | await ring.nuke defer err
702 | T.no_error err, "nuked the ring ok"
703 | cb()
704 |
705 | #======================================================================
706 |
707 |
708 |
--------------------------------------------------------------------------------
/test/files/parse.iced:
--------------------------------------------------------------------------------
1 |
2 | {parse} = require '../../lib/main'
3 |
4 | exports.test_parse = (T,cb) ->
5 | message = """
6 | -----BEGIN PGP MESSAGE-----
7 | Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
8 | Comment: GPGTools - https://gpgtools.org
9 |
10 | owEBPALD/ZANAwAKAS/gHEVDSNo5AcsMYgBSzEhJaGVsbG8KiQIcBAABCgAGBQJS
11 | zEhJAAoJEC/gHEVDSNo5d5gQAMHe2bPIFLL8wdu+KG9rkSqZ3iHloaHTkhN729T1
12 | +OefN1YeS7RpOHMcptNKtu36f9LFeDUCfgeevXcL3v3f5Crvl1TCmAft87HlsqZ0
13 | 2L++qULRkauu2+HYHB0tr5RwaTYH8A3rYLD79Atrh0XStHcsh3C6ISmePEl+eStE
14 | 3uhaEZ+r/PTKxN7/+qh8tGQuhRTI1fC/3rZmDqHQigTJm6pBvy/kDeATVdKevpbZ
15 | brM+1jWITs+c2UkbZLmmIqHEe3JZrkvk6wP96HSTZkopMtyqMUnkdZLwe3MsYtPc
16 | aiB3xGD5K5EZeVOkYAyfbpm0QhuPg9sNF16D+qhoie7vvVOeoKA5wpI8aQ6rnbCc
17 | o/sUUlcMV1UaqeOqazXEEusAzw/Mh6mE7FIgW5f2DWzmu4BKcvbGJBbtxVAjVX6x
18 | omaUjqVVDLXahbZefwC5VuKYe5DySzWFGVCoJ1Jh+kNtvRXRBzsODA2tQcRVKRxH
19 | pYfYhCSN95qFV+EfYuuvRLSsUSqn4jDE2QHzxl6zi0NWvvFWPLAmr8pitEm62E8g
20 | AUY4cbCb+KksTAin1xayDWYuTsmLaMSBkOdo7/HElf0y17a7+FbNy/lzlMylcs23
21 | OCRE6vCa7Pk9dsHC7OlRcG5rEGFnKuZfnZdftM7nUFNtbIozdvGeJzRn9a4roRw2
22 | fWti
23 | =QoKV
24 | -----END PGP MESSAGE-----
25 | """
26 | await parse { message }, defer err, mout
27 | T.no_error err
28 | T.equal mout.packets().length, 4, "we got 4 packets"
29 | types = (p.type for p in mout.packets())
30 | T.equal types, [ 'compressed', 'onepass_sig', 'literal data', 'signature' ], "types are OK"
31 | T.equal mout.packets()[1].options, "keyid 2FE01C454348DA39", "option correct on onepass_sig"
32 | T.equal mout.packets()[2].subfields()[1], "raw data: 6 bytes", "subfield 1 on literal"
33 | cb()
34 |
--------------------------------------------------------------------------------
/test/run.iced:
--------------------------------------------------------------------------------
1 | argv = require('minimist')(process.argv[2...])
2 | wl = if argv._.length > 0 then argv._ else null
3 | require('iced-test').run { mainfile : __filename, whitelist : wl, files_dir : "files" }
4 |
--------------------------------------------------------------------------------