├── .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 | mI0EUmGJaAEEAM/gj5yRV4q/3GDTJE+WammHO/UBUhUymEHD+esXygk/EK59YZDf 329 | ropicBsnun0KJGw/I+0dYtUlEYI9JDjtcmXLJiRQ80pnf+9Jk29ZDZfsMOOBzMqb 330 | TBaYRuEhimi29Aqg2xpjz9Eg6AjcY7AxbS4rNij83f3oKAOOlXq3aQilABEBAAG0 331 | Em1heHRhY29Aa2V5YmFzZS5pb4izBBABCgAdBQJSYYloAhsvBQkSzAMAAwsJBwMV 332 | CggCHgECF4AACgkQhPz25wF28buuIQQAzE4i3C6/L1e2/x65uZM6do5QATH/mzRa 333 | 0T4EhIg2TxXjnFALTNeswICH+M6/bzsEgTrT4r5NGxI9wDEsJbBq+eOYZbkdVtbQ 334 | T3MgLmrfaGMPW/d/i3yVAhQMACOVfPtIhHqgoxOKkrBrMJpJuz5MHF1AOHyLHjks 335 | YUzL9GU3qkm4jQRSYYloAQQAvlcAkUPPa+VSJxfCjV8VKgZkz9I1QoBHcr4GjLpZ 336 | RCRgaM8+QDgsS4Dt41c+V0/TnsNKZsx39PgYnCozfOj0VkZFYkUlKaEF3AJqz5En 337 | lelKdQkMBy5u1pwVWe5EucdpEARTnMmrSqi5u29i1GzLhdtFlfIdcRPObL1/SIFK 338 | ch0AEQEAAYkBQwQYAQoADwUCUmGJaAUJAeEzgAIbLgCoCRCE/PbnAXbxu50gBBkB 339 | CgAGBQJSYYloAAoJEOP3tdmxSNwFwC4D/3UNnN1socTcSgMeuB3t+FbV4UklhkVr 340 | nCxbDdy8J277uG1dX1bW1BK9yLOTpwHZ4jp4ejlVLHgPIvjnqGQgU1YeXnu4lrN1 341 | ggPk25CFHwxjB94DGBSF8vE7sjlo2PwTGx7m3+vD+DIsCXvZ4zUvUbERrch8z0EF 342 | 6MAW82Nvki9MrxcD/jPo4jkgUrBC9HeshjtGUAmjN82Ecx+BZh+lG2Q928fGZiCA 343 | KiJ05RKxFoVkS7pWEdJoi2RUS7qsbcMjvpnZCz12H81AzQn/JvrwV2RHz/gy3hze 344 | WHykIL9Y24Se1lmxx510AA+n1UiRPjVJWl48S2cXBtAshvNT21MmWC37cWG+mQIN 345 | BFKLi6cBEACcP/W6NBY1Dy+1Tm6LWOpPGbP1DsxP+ggIA0LmxaXWwL6g/2KvoS/J 346 | VmmY1uXIiiZoMqCTZq1RTlQP9wh/ky61XxZmElKxiWKvdgVql5XYYQxJUH+6vJHP 347 | dcLOQeW6MTlP/cy6r6wFS4pOZ0I8gquufYcSp3IiCyDRfGndfZno3YABjC4QqtTw 348 | PKMh4o7G4ScV6SAKWG28mHF02BkXTBlZCWmhI9foQWu04I45m6Eg00zaS2dYX8nw 349 | U5H6k1N/3RUMYCJVmDOMl+p5Aml6ZuXhnUv0ma04yqeE0LsbVhsPOcWnVd4F/+x+ 350 | RJlhqM7v6j+mi5bSYrJSxzaXwBjQdSu/yTKsao799EO9Kt4D6D96Mg8AeBp7tsuj 351 | OfQDCc4JKzq19V8CSbE1iwyuqVJOytp01guljwRedNbcAWcMkn/Mv4M4HMV8push 352 | RX7guAxZrjpCgaWabThRXuhUhrhMXZie+kDQegczbuUG1w2RO/HvJecUX5E2/lmE 353 | j4NA/lc+Ejgr9NGczC5Osf7TyJhcuaC9QMm/Mlfseb2d9DRb4V6ZOC6akZOlgRML 354 | lFwWlfduGNGq8HWBETSJvMoh9Ef9nfEgdBi4Pu+WVAd3FDCbfhNj2SS79R9WPHAg 355 | qHJD1/qv8NV1u8EmvuJP+/Nw50Dp5sBqoi5RTYJO+iLWaY4vDyMafQARAQABtC5L 356 | ZXliYXNlLmlvIENvZGUgU2lnbmluZyAodjEpIDxjb2RlQGtleWJhc2UuaW8+iQI+ 357 | BBMBAgAoBQJSi4unAhsDBQkHhh+ABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK 358 | CRBHSE5QZW0Wx+MnD/wI249WjV9x9hW2085KtRQK4U/79Y8BqDrw1JW9l0oppPZ1 359 | n6ZRnpSel31IucC8NupFA5AyK4KuxMNej5KLF2kaqdgzlcuvSA6npQkyRnohE/PW 360 | CDEJg/GE8DiqzMQx7yD/7rQp8I0aI9iX0SJCPqohuyYNVFBEamcLn+tDbH4U0jur 361 | PuuAKtRSGxjzhnEiPM1hTgbQv8A1FY3IPClAfXlOK1RWI8pXSWfJFx+hT0ZYR+mS 362 | BIwLhfobvip6yLM6I47IMdLTzi0ORatgDIEk5VHuHscDvgukVelmAql3dq4OhsnY 363 | sT2G04r6L8Ksa6DKY0Y8QpQGjWXFcWp628f/XhFl3vaGho1nRxMcafvgpiJABrdL 364 | mSl6WDEwXFYv7zozmU/6Ll/gLLEAcCSTX3+JLgqghUbR2CXTTAf2nD7oFOEp7p1+ 365 | sVyAPMhN76T+lrVVa9OZ7eYNwIUTp3VHGKlARI0kQvAs04H+PNOK/S2e81hnTqMj 366 | 1MpXvfJw6RtCl+An+lIrAOJiORxZv0tDgwm/u2DZGV4oLNBXGucnqjXMBj1fNcd7 367 | FDcEiQ6CXWvmJEGNFsaD0tKtESTR/dzkAzN643qzd0clUV+N1TUD2q+LHA+QKk/e 368 | nBlrPgW3VQEL/OMK0ugOMlmFBy4008fZG+kt84BLo/3Kpy3tMy7fLbFuvlWpWIkC 369 | IgQTAQIADAUCUouLwwWDB4YfgAAKCRBjhHtLg5MPDMNZEACOSu3YQOkNIjmXhMPF 370 | WtSUB++ktfhx7GSmeBzFx6BIJc1U+Kfpu4Cr7tpZTQk+k4lcmrCsZmkBElgDw4vW 371 | Q4hOns+/L7ZKyj1/XyalAMZuLovzZL6E8MU7BycLfi2bvP/bNb0Jkm+e62T+gzuP 372 | dSjHy2RUkr5Ofe2cvnzFc0cjzQPyOfoOkSB68OOM6DAAMbt7xZs7iex0iOxlNSYx 373 | EOsw9+CBiHuLU08hnv4PlxfYiNouBbgeDEmB/ueMQpV8uKwN65rUYV0UHY0QhU1T 374 | EwgBdde/D1io2fLLhWhxxLK+k2D5Kpb6fHTbVAvREWyjg93JcRqiD0vZqyE+4t6Y 375 | 9Bjc1nKkqHAz70viuxusCBS3zOwHInatOav0vOlXO67JNcdGO+HHv3+4KUeQvqsg 376 | EfWMfvd48mjeZ7sU9Whlq0WJyeeUI2TRxReioBay5IcfysBh2s/G6rLKGuiJK7Rj 377 | ixwkrJPADbCoIhJCivqdTMAG1HgQN5an6XDfYemwECJCRK8QTI/UwNVdt7p84qcY 378 | DagrWiE7fB8MG98peXRVL94ROY3PCBbcWs5lFIINmaaHXOBGMb/RTh9EjxNBWwyi 379 | fIsUkYuUTUEnzH8Ctdb/dcq98y/47JfaLxbMepC2E0bMxFZTtnDbhXUbXzongY5f 380 | 180kFDHOB+UJtKL7DUgFs5Vm3YkCIgQTAQoADAUCUouz9AWDB4YfgAAKCRD7wH1q 381 | lwFss8A9D/9XYIuv7nXk+xaoWxmAJkHgNI2gcADLncxuiCZn43x8eD7xqjU8Dyr+ 382 | cFuHTldfVDDR8EaUWjGdmOrLNILgCIzmddaBdX0vf8zAvtSWCFtf5PSRpCINFyez 383 | tA9kETjaSpsJLkdX6dMLnXOTTcP8ZT7mglIGltN/ys7SLYzcQufkbgLt1jLj1dHK 384 | DuG204Z/DSXi0olnFsMp+JgnX4D5LtgfivF2m2DwMjZZSKoGP9ZVI2CaHex6ar1H 385 | 0NbbhOZQeaguqnBqm4xKOAY9vKwYVx5hsZLB8E48iCMyg3J5cfIGmmGNpT8V7i+K 386 | 1oV8TLl7KlTbVpF+Vq0HYGmdEpR0r1tE+HVJHWcmGtm0gVshnXHRxR540g4UGnN+ 387 | ykaRbHoy/6JGnct+4+E4YDcR97q7kwZtX+OfKIzO152WAp/Rdobea0zdGC12QK5c 388 | JA+krYeB2waFFvuvV/ewUUCMwBDMTqhmrMoRQIcqsjQlGOUOyUIdO5BiTfjJ2rs9 389 | pgvErTvenFZLKU6kp810iboHk2D+8eaonLhU1q37Iod5yfqvFuMRW4k9QS5+Tbpe 390 | TCfRJ1VFZnSg9FpAIXzdMgRTNVrjvCaP79yjbEJX3n9X9isJkKjcYMKFG8CVLm/8 391 | LLdfxMBNjOXIYtNCRpUB44AmtV0dzFH/XFQBLngyL2EnLWWy5cYEs7kCDQRSi4un 392 | ARAAupbqu77i8emuB5zV20nHVrjT+bc1cp3iqwIakK/QBMNNjzefbHUpDQ6Lpnu1 393 | /bykpO5UXmd9iBaL9nzMFyZR6bM/c7mjknaD0sV60aMOUAOwE3syXtQ+Go/J7dua 394 | bWTXbN8VKiEAS+uYB1DJDvwJjCNj7viju/KveBLLHZiUdQlQXzRdnnuoPI2AZrKi 395 | QjaUM5kLiYTyxKiBVtx3IX9khl9zg2sUthWke/DiH9W6l1nKYFkxvxheJTJoGLFq 396 | nBUrPinK/3TsH/sKdR8+MfgUCkEN/SCRcsvczanMogGO6O09Gb3F3msLBX20Fs6Y 397 | MSPXlZSI8odLAJVZnBxUjfwgplx5paqkg+1Yv2ok096MHzH7AzeCeZv6Nyf60Bij 398 | rheMLOMtaXialot9ppgIoNMQcyretXepSPMgfBncq3o6A2FSHoiVJK94CEbCARMv 399 | LrAiJfBt2SkrF8L+HaSOL8Ebt7yw85EKZjPhbhAA2Myv4FlBDJAPZAiUjSopkK+L 400 | hSaFomQpjLHP5ZA2GvKIJn1ukgqSNrzdUcwR5SjxXijb8ecRRW8z2WCHT4P+W2en 401 | WBKpleaDlbOf6vv+zkLDD9/OUMKMO+/yBdKYbW6N9IexIvbXy+mRv9WfSq9wG0N6 402 | frYg00bp1H0F0D5kkAR/AMKTNyCSRFOqAsRhwlsgAy+W0qsAEQEAAYkCJQQYAQIA 403 | DwUCUouLpwIbDAUJB4YfgAAKCRBHSE5QZW0WxxlYD/9cCGSNKR5twDvF8bI4TzGP 404 | aHlA6IgY3yQGnbrxFXnY8XTMZXUgR7hVuxeoTsd5LXm5WilhydIIRVtSijRkrUC6 405 | POuFbHqOodLohpMCyumFi/HVVbW0MEwwrFoyRm+aXymZid0nkHHbCO7j9pA5CoGq 406 | fxNs3huhIYF6n2Gs+VGTTnUcrp/VkmwuJewiHFc6M11WKQBgmkNEYC0XDjO7aPsC 407 | texiYryjoryf7CFHCnc7dYaULwB9Zp8OnZiBStSXJh3cWHAljiK3l0KQJcQb9Gfx 408 | osY5xO1UkZSAgvwUbaU5qRxn95MOem9hnDkDGm6PbMZASi5scdE2UkpuFhNEFHIg 409 | Om1LP5gKgdFHFeXYSd5ODnAjKnv0bcEW7E47+7ySX/snPhLOpbOW6K+X/JWMOVVi 410 | n+5fk4ozJvWyyzQzr7oejmHcSoHsVMf5SUCzI3GPBPvuZKuAYwiRROFL6jNwqU+5 411 | xAyRmiHr7PRzs+RDIeGWCSFqSlzvSk+WWCaGaD//uhFQROlX/209moyiFe2r3g9P 412 | CZ+43F+uzn/T4DRpmOzOESxMySFgGBK0Mz8oo5ETgMtr22oHgCtdnjQ7u9x9Ajo8 413 | SVNv+WMeFj1Xd/VnD1W9qByJkmnrUWd2WLYQQ8ROJDPJKPYUKXam03UJXZidoJO3 414 | q10K/XeP2GohBf5q0imfU5kCDQRStxTMARAAuF4E7x4OOn5lWUEEBTbhID9C8H6H 415 | 3PLe1UG8/LFCrdgmVIAd9fJh3nJNvtJ00mrT/UVjxyM5vUpiyTBsr/o+lmCiHPh5 416 | wN2yKA28bT/OBMSZcixcowNWP4+p7eiZ+EzjIswsANyYdAeHZB3GJ4cPeo2HKzxk 417 | u4xY6kKNa7OAfq0mJkyzKt3bwO24eub/HHoJfYAocolBTuo2OYdbC2s52GuLpR4H 418 | WC/oAAUJEWMcX4ndzq0PDoTiQeqEBSvjcApprYKkB2VW/MwEEXsffIpk/4f6sE78 419 | dGar8VXsNsOzJGg3pwBODoMZjEhqE49NKlHg09NwQ1ARAtYkFiIp05hiaYPsibnK 420 | NY4bwriLgkypOkuxRgsGE8p27zreQD9R/75wy6LzQ5Bfv7tMNG6TekdfYATVXng9 421 | RExxrIVplH7uLcvLv7bz/5oPev9pAVswgbLVzSvNno605QCgf6mkFuzdW/K4haxU 422 | pgu7HpNQie2mqffzbbjESWant4PouUhwIcs084GyRH7Si2oNZri4FInyIEkYQuUS 423 | Bg1vdWzDtqX4YCYKE676cM++2lAToTIifTD5WR7Wr2v9/vUXz/tpMsUeas5xpwc1 424 | lTqC5R5P5yRexuK/9uFpwgMSFJAizMtXQnC2W/+awiJbsbee5/s+40JlvzvgBOl2 425 | 09ScjHzPIJIbZNkAEQEAAbQsa2V5YmFzZS5pby90YWNvMyAodjAuMC4xKSA8dGFj 426 | bzNAa2V5YmFzZS5pbz6JAjMEEAEKAB0FAlK3FMwCGy8FCRLMAwADCwkHAxUKCAIe 427 | AQIXgAAKCRDJomE5cPucGii/D/9yhUElgTx3q0AUcsPWcM6XdWoaoqvzaDYGkGrp 428 | +niJqvEkHDh0NrjJ4kyKTGwrkGVsyZYPHovPInBMyss/1RPMV9TiEWQCvgnxWxch 429 | /DjEcfC8ZnQbbW2JGdEiIB2b3KKrc37rGyKvMtZI07pRYmrIT9FjgmJK1P+PtjaW 430 | R8wz0ZgvP6PJy2P/iADdBpTWIdr83y/N92nnPGxNtfvX+OB13w8j/2XFeBbALVaL 431 | DNOo7mwCIuJRCF61p3bWUjMO8gCPjZVyvH4aM5jb3Cniby8FXEXf6ywZbi4DUQWJ 432 | BufhHFg6E/EaWKEMqOZUecySHM+HypfcqHQLcusidpPIwvk3Rka/wjXK8URU2CYs 433 | 9IWsn0N2aKlPZWAdwrDxg5hCRuAa3hcJN4QEJOLIyAGYhj6cVzy4MHYhbMeNhUtF 434 | j1QNqPRZ3pFUw/82WK+kZA5cPB+l97D2LGqsfQWUXWJiqjXx+8dUPf5NVzG/APJ7 435 | dICLLFU7Z3A/DxY0vVEU9rjc61c112QA1E7zmij17vN5PFkNyd11b9oCvd0CprN3 436 | AGhD1OSm95mUhmOh9XOA1E7RRkDCKLXxG/hKmFmtqoUDHQGYhisGSodxoIMPtWz2 437 | RqWpLsYmQh2rv6SwhKMNt9WO9Po0UQnNM+ZVa/oB77V0TZT/Ze2HzAebi1Cec9BU 438 | gY2SlbkBDQRStxTMAQgAqh+qfZZCFtMpTlCEQhvjDdRelv+uLCniBI3lfqqX1GOl 439 | MisLy/XeVgoN7ROtIPd878PdooISu9dKJDLbMzj/ov47N6YJ+fCytuSm3qQyQiK5 440 | 01lXKIyaid9WaBcMHibtnrF19eVKDMaXd54IeXwbPu7XyUuQomyb5XXq/ddg37l+ 441 | fPrWdNPqt2Tfjw7dG05AyHBXGOsSDYBIhmDWODsDiWpf/ExQ6m1gsffazgdV6Fn7 442 | 9nfRwdw/ubXkrAiYmAN2EHr+svv+ypknlOe9BviaAQ29i5mSOehUKtnvxjBxoymW 443 | nkY2lzk/AZ5iPbe4QaNpJ7Ny2MGa2JWdCzMCd3pweQARAQABiQNEBBgBCgAPBQJS 444 | txTMBQkB4TOAAhsuASkJEMmiYTlw+5wawF0gBBkBCgAGBQJStxTMAAoJECKgfTmK 445 | 9+g9fZIH/0e5w4g1HJMruqBaErrNVr9gL1hdI4ru4hJQW6kMLxCFg4S8Yp/3S+I+ 446 | 0oFKgUxAE/7aVhJzWwd4PF42f51YpIXyFxjtEBUgCUnksodnjRngzHZWnZk/H59j 447 | 4ZSLF9BmpBbj3uMvCSsllouHY/zKQXAYoJQ1SLl4sATu1Og87DWLBfaOtITVbZBK 448 | LlCvyWFvM9pCoRHzlHtvTlwz/ol9rIWm6CRBDToUX7//5iOBDEEjnhwDPZfhzdSQ 449 | VW4lhrzaLew22EgPteFeSu7tuZJLq1S/Eg130EUVKVut3N1TljE9KT1Jqty5NIMC 450 | jPDeS2AvA+PWjJH+Yr4tfA07LENZJTdoSA//cM7/udNcuipf2uK9AT+kBASL+2FK 451 | 3jlg8VRLNivsyKNgGsM3mNmYmFhZjJt2h2iyNUyvoanx8KmzcYIq9sOlizB/5Bkg 452 | j3oLytUneRe1QZFZB65cruBb7C5398Uoy0+6wEYXNNJEF+PBupUGJYqomf96zaGC 453 | dQzah3EAoH/TXw0Z65QYPxBrYds5qsAPeffcn43XII0imO0ybPo7CPPju8luL/9P 454 | 5xZWU7xBZ6NCu6FzhV0tvOZ5Uef7teDZ2dgERWHVYvw9irrGvAudUyv6HDkPS/n+ 455 | i7HbtUzXsnbZom3V2NizRa2AootKMaiU8wuqvKWMUic1A/hY/0VOWGix4tuRdnZg 456 | tpCgrgv2KtUamyQhu21xkuEoELU7o5w1Y2hZDc/8CL6GyZqliQ+BlIx2gQX96MbQ 457 | qGc9bUOfqzmU4YqtWWLO5gju+aq1ZaVtZ7OB5SrlNvRK4aRAQm+2BAdn1kKajUjQ 458 | tmS4SGEN27IJ6DZotbi+T+p5oKQbIds1AX+Wk4FmnwRcNFH4LwCSIYvi4tUyH64J 459 | S4jpXj1FRRC4vJk8QwJtzpHMwXQiKGWp8uN9JrFTdIOH9Eoct+hLaoNSrg0/6vvD 460 | bysCqR9du4tf0d0bruWv3o+mgITvaKiteSVt0CG5vT0PQsjZaZjbHGhoBD90evWC 461 | rZU60TsWHtBGGoqZAg0EUr2rAAEQALiXKWxbP3+5O58zBTMKoQh2kK/hcp8deE6S 462 | 5vwrSPkRTIfadPDvlIXgVnaSGZl5H6lQDVOPCMK1UBsIQWBRwHxxi/1xcUFy4+Lb 463 | odaPAUVAztdk9sXNyZ3iWuBcZ05O4IShwePvVGq8a9wq/qyXX7DeEAdYeDVTBpb0 464 | 4v51o2AGMpfJvt8SyTvVYHFqfe6sUKz/0L66MuUNCV8yXViaMRY0hSDpeYt/8LhF 465 | 8y25aMsn0gVkcNaP59sC26yxYP+8E9RozYgNmXBqiGZO0JIbtJZ+tTvVtg1pFnnY 466 | 8hN1Ptbz7O1eOmGJdtX9tUtHKJco7HIHFzqHBmQnPhlGl989amb4XWrdlWiYiEG5 467 | Ftm811QnNGyac9sHtF3QBfKAQWF4Ns6WB/eZbW1UiY55piBwxLtzKpAgzgSvs/KM 468 | rirooWFejvLL5hk2nQjYUYVTRINisdGO30F7lzluLwNiDdBA0IfflfeR46dpqbbw 469 | xe9AKLIqXCpJ1mpsr9JgzVS2mq5hzz9WMI/JyfATaT+hIhOPUjW9SuaYpmLTH3xk 470 | oWu1iC3kyGuthl/WkqCPEyLj1IE8+L8SXCMp/qZw2qj42j5/q4DHC2UYIy6VqUGn 471 | vZ0OwObCFwzHCJrd3H0usyeMLNaJnXpu9yHSpb0YkuM61KEGF9oSShrPtkkThZtP 472 | Q07ogKy9ABEBAAG0KmtleWJhc2UuaW8vbm9vYiAodjAuMC4xKSA8bm9vYkBrZXli 473 | YXNlLmlvPokCMwQQAQoAHQUCUr2rAAIbLwUJEswDAAMLCQcDFQoIAh4BAheAAAoJ 474 | EG+wvnXAswGsq2MP/jpmFIJ/UQV0WGDRbg+9IvudYMvRSQ5D5Q2m+oZw8Ks+1rhM 475 | 8lrYV7CXkYtt+5IxeCTWBWYIK2KtC4ZwKuoBwMCJhSiI/tlQa0R+KkvVVZZgSkRp 476 | 90AslGSkDLoEW+KPtT4BFLGHQ7N+5Hf+LWymNNmkDpXnL1/FqX3tJCIafT3S6Htz 477 | XJOSbBFnc06uZMrvK3BDcIQaPd+LHfp1mRUy0/UleHBQdpKNZvxwEX9oYnl31KCb 478 | mmfUaSp598s0cEsSLZqHQjNeCrh321RrjOd++yabnZHvBz+LvF2GgZY9bbDWi06z 479 | 7HlvoLVsN1taEd5mcT5p1vQpDT5Pcp2EZEChdZyoxfdqsuQBXW+rTh0p8GvGyLsS 480 | aELIyQKXpJAGcKt6iuIxIp8S6TZRPj4iXIJf5ldNJcu1ql0c6qgkDFJOChXbO1Qe 481 | HgRPTY4VxcpIURugVSVi/mopUj4pKHmXwzroDDsEU2VuWk4+PdSH/tYal8bLGMHE 482 | MiC7iBGiicPi5Gcl8B6cwiVEvCpvGDjOHGlxiuGDRejpLDvinYSiMyRpPgWhFDKu 483 | s3BbPuAgyanPhAhwUL+6X4gj6KDtfCGY/dhb0ggBcHKuuxC/LizLXoGFzzwFa7+E 484 | FQFB/3YGaUbmJeASpGMvge/UGR4tTMc88Q76sr62y1N8GNWCdcYYMJADQb2ziQEc 485 | BBABAgAGBQJS3GGjAAoJEJOXlbBgVVuCaBQH/3KvJ1qzTDNRz5PXPnvZfh8BkI2z 486 | iEL6V5w0Z2D7EBPKpH177u7bmTy3HZZAJWtf0bafEjfnT9sVrR7pWrqvmCRWVcC4 487 | /kQymVZTyeD4gxZy+fENimPj4LP9KcoRt6qCTrBC5RQjto2yvblSBON9lmOhoRkh 488 | SidDiNrbUb6MU/DtWhJPGfP+Bj+Wdz9VjVzgtOFBqwDuCklu5j+O2kYkee5o/f07 489 | tvt+7Ah8PHZvbFpy+vnDE3t0pt+2LtiZo2AnObMihJ+1HQhGjGAOphxc6VfGeE6+ 490 | pgbh66COidmnqpDT7Iuh3lA8HapIS4WQfsVbKewBZDU4XL867lFXafUUoZ+5AQ0E 491 | Ur2rAAEIALxOOhCgFc1ZnHQzUBntgD0byDpKnRQ+GgKjNRp356MhPtuPgJK+RDBZ 492 | lolTn2wavk/a6MckpDA7KwageGsJ53y1UmUKvnKjtxBZiSO+tv4AtXPCG3zzVJcQ 493 | +b8aEXjN/mt/efLtGGYHPTpF7C9a8mZgB60Na4vWj4kPuxmgnCdy7o80i4tmvLgc 494 | RzzEKdTThCaKxi4UJ55ERxA2M1R2E7/gWHEQsbEByR9YT6NjmluvrQeRgSfp1kyk 495 | akiKgBcJCLxc43Y80U2NhpQoLbIzITE9N9bxCJ7wV80vYLcZLFShBgfdEdqNYhdW 496 | BvYDYiE7hnyrk81yAvToI+33KJ9foFkAEQEAAYkDRAQYAQoADwUCUr2rAAUJAeEz 497 | gAIbLgEpCRBvsL51wLMBrMBdIAQZAQoABgUCUr2rAAAKCRDjWCbO2YbwrLrNB/9j 498 | PO7oJF4HQl4iPjR/HWarA6M/6YSfR28xFgId5Kh1K1WF/mopJUuD9X981pT5T8gP 499 | sCxLKaQG6BBgxkUsyChO88Y1tdMMcE9TV4K5rdShMDuN9AT3ECz79EnckFpXmhbE 500 | 0x0zcieqOKGoxa4XqJJhCuMLha//2WnWYcrNoX5U790SevfwVd/LyzhvPNjnWrXp 501 | D8ERtcbXyyD9gewo3VGLHQ+VGdy2+V9xTI6scgEk2XY99er7XRq4H8kPvDebYkvT 502 | xAPacx5RFcTok4X6T0ebFhCu2z3BugXxkrYsWoNrhzciecpNYMAxghatU0IW7TJ5 503 | G5t0veI7wsg/yaDVFZSHmZAP/2yY3j06qU/Z5whOJ4Vn+xkdav947okiJqcidrFW 504 | oEQkH0Gqxxg6QahAJ0d/lLMc5hcEYnDagivhI2ZyGmKehKvWMCuWUi50bABDSwxm 505 | iS1Di/KrwibyVAzYtg3cXpFy6/ifD0LM0d/cH7167+nymhL7o+k0ZbbZFM/unbXZ 506 | +/LIxXiF4ZsAn708CCG0EAltbOO5WsrYeoCDCwqAu2ds4BuWNkoMRWnSgneSYxJ5 507 | nn9nvgJ/cDRISdW7YeOtrUMYTDXWsbVfxD+N35+sLjjAKw3P0yKVQeSW1c50iFGn 508 | VxW0jFmYkWWfnn1+qNMrTkuxo64W/84iyCm5PTRQjRSmTPFkzxdcDJN3N/scA+6v 509 | 4CvDSCG+uwVS8p/6zQ+S9KKIdzQjZUdr8OLyt4FQsKbRT3ay24EdGUPN9WTLi7zb 510 | L8l+j8sgpNe7zVMUDQXaHche5zjkjoPudHxXOw92dIK+JpWrXX9XYr/pnoWLQvpH 511 | yZovEBckU8aXMfXLvPd4UqfocCvLFmwRxzbQCsL226kbgFWTKE6X1AfJZ30KhOPX 512 | 91R/9as1GcEMu8TeFVsQ625N3u2zS6zcOIGs+NYoSQ3SDHJSgTJ1U2r2q6CHdqlA 513 | stPvZwHgsX8gRVgJkjPguUZzKD4qKs0K08WwHXFWHb+rCXZSYk23BjVqD8ieQx9M 514 | ao11mI0EUtFEyQEEANaSng0CiUjyoBAoDdOIIKD6M1gOR91rg14xDG0uC1DzBvSP 515 | VNPQzdtr94+bHH2g2X2WqeRvzJSLiyX1jDUw8R1nWNKgIp3tfVbHHYyGPUgdTJpj 516 | /A+M9JVhww6Hu9LE7CS0hAKSQmQF+EwkthnQJx9pjD2+ncgLx6flunNEurBtABEB 517 | AAG0LGtleWJhc2UuaW8vbm9vYjYgKHYwLjAuMSkgPG5vb2I2QGtleWJhc2UuaW8+ 518 | iLMEEAEKAB0FAlLRRMkCGy8FCRLMAwADCwkHAxUKCAIeAQIXgAAKCRDBSWVL2KLA 519 | gNO3BACVfuhJeYHMEoRT9sb97n6T2JyvirsRCFNB5hpyN23vwVgOCBF9AOxf1dnK 520 | oOdyn7+DZrRBka3uW6hEEZkhSvHkJDIO14gCbQpZLUF5HMCDYeGZnAabF60vCXkQ 521 | tGvpGlyAiq0sR2z36kXgMQTHTMYpnOyRRpaJr04iRgaCoWV64biNBFLRRMkBBAC4 522 | nFuaGeYC7S+Tp8yEuE/8pHdFK5zcPeATpKTsyNW1aKr4dPzZZGeF1d1viqYqkGde 523 | hn5eIWe3T7oS4IWRfyy/WtT+b99pxyYU6GUcPh9hEe+2Dyi8z5rHpKRIeFzkH2uH 524 | KQ3BJIt48FxSsB2qD/8Mpye/6GokDRV1Epytrxoj2wARAQABiQFDBBgBCgAPBQJS 525 | 0UTJBQkB4TOAAhsuAKgJEMFJZUvYosCAnSAEGQEKAAYFAlLRRMkACgkQ2hjfSkwj 526 | GvHX5gP+L/YSTOf5tYLgPdmTW25axQzTbk3MTSYDX9WOb1+KS1fz1fwVn879t2cH 527 | UIgzaey0zJRwDo4kNZNVoha3V6JNZDEaXvxUCGN+3gZoMzUNscq+vVtjDMFLqHSK 528 | lGTSTrFt8Rg7sc0Hs2T+TIeiPO8N2HYlqXtMVLZzvsnFd9J9QBwTDwP9G6AWF+MN 529 | GvpggymXGPpcyy1oGvFJhsPfTitV7AgbzoRKBUaTvAJ1Mc9shZxyYJp9+Y5KPwkD 530 | JW7kaisxUFjCMuT9qybhT78VoYzfsxHMfHjUo6ssGdl52oZm443QaMn36N/FBBi/ 531 | Ecf1Bwl9qowiiXsEDpnpwgGrfrCzUhsLpcaZAg0EUt1y6gEQAL9yOhXtml95Clrx 532 | O0LGFx772mxIWruqtWsodkNsYQc3c6fLZL4/7sMFlzoft0goYG0/Q8PMD8ssiasv 533 | /VpCF90D1n/KBuYu81cosiO/HlfQMCQlBc7WMwqdjOd39oHOb81MVGMdf8m8msk8 534 | iZMewAt9vGMv6mjkau/+0nBKVPnABc2suIz3NFn+IkslmzNaNBr2Y2c6QkPxCXwy 535 | FpmrNxSNb75KP7JLO2m1uUBotcyiu92s1zW77akfrjBNtbRc7PM0caNftRfnCx9J 536 | Afom+zF+ak0YpwLDnoRqP/rZDEwQcvBqTJQJJhzwArZj38QNh9uNAEaFh6mBSZTF 537 | c1Y8si+0EEeixqDCqjgXMziVYJJAz09PfrAGPQf50XwQr+sayXlmiH5n3YJpztAR 538 | AOujCCFcCLMRjtUGaGGZHsY8aW9HBeFASRm+4pNXilXGv19/yLuiJqwJSPQVuhA7 539 | Ja6s9Q2EMrVX6taIvqF3sTHeEI9NHaNSi+EhLxp10mXErHi0T69qbAws5XgXrqN8 540 | AS2laPu7zAovvWJqk0AgaxSwl3S3gq+TG/kYQK1QnLx5ic0mbaw5DrXMY/1HAWAV 541 | X2dGKQ+m1viC9UKUUT2x1jdfHmM5J30H0R0At3VzlUazsyCQvbwjtWj3aQmb9jau 542 | NJgH/Lh75CATHze/ZB3KC5aSm2CbABEBAAG0MEtleWJhc2UuaW8gSW5kZXggU2ln 543 | bmluZyAodjEpIDxpbmRleEBrZXliYXNlLmlvPokCPQQTAQoAJwUCUt1y6gIbAwUJ 544 | EswDAAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRAZmiWlf56L+tLvD/4vfvxs 545 | mWoQBjgS4co10SpN4ShIU6WAWCjgImIm+wBAK2NhRE71gISDu+HnU7szQwvQh0ga 546 | rF/37i0BtIqp9iO2UI1Lva9vrX+Cv5bx6dvzMPcq6fx3cY5RBiuPukO+S+W+eAqC 547 | i09hdtIQhJJ0PQ6WKBql3z6diKfBMy2GfOol8I/2qNzRh9vL92Lf892pMoqqCfdW 548 | s6wXCpioyKE7T1wDf3b0GeYg0liD96hKKRfFhOhm8kdRIvQYC8fZ3m+B7/FMtWXQ 549 | 1lM4z85ueKQ7/rLbLhuz9+mKR/+//323YX6/yRyFJXkYdrS5VcHGYeX7uXB8uDd5 550 | uG1RTf0Hf2hJ4QMOMX+Mk6991UTFFEpZfrvsUXky35LjOQu/QJox4SWh5DsVIlyN 551 | InDbe7DDWnA91N9ntfYOEJyvZxHqIoUORtb06bkfpM7njJGoLl3YrvAxb9BGsGx1 552 | qlWGgHAmIXVsF0Tl7yaip0Gj5+C6tv1JUp30dR8b/j/tplUZq6vfJ55U2xLg5+s3 553 | Op2YcOHWy9hH+kElF0Wr9kErlP24qnC8YfZeJuHezktoqIzfW+JNnkqDfZFK0hsQ 554 | 9H+UOB0fQWATOt4nmBXO7nnyzpMYl3GQrzPNKV8698ktAGQsNq6TQ6Y1w3VIi/1R 555 | xSWpnHZ0yekBCjt0MwBQiaRO0pX1utG0pA7e+okCHAQQAQoABgUCUt2KlQAKCRBj 556 | hHtLg5MPDDJFD/9LGmFXQktBiCj5Kn8nng7iVGF/P+N8CYtwmWRiKSfWzGwfTnu/ 557 | Hz7A8lR45BTS+FjZMy2rDghV2bbI7niH4zBO0auulrA4t3MG23ZqEajXlIl8Y6Ek 558 | yzk225oxnc/+Sl07G5lOmaqGY+ArwGA6nhS1+aOVlCuJzMZhWc7+gvCF27XjHLS4 559 | OTJun92lYQEBHro/g+pV9xw+gku2qH1QqIihPZnFqULhygLHGgUKRiQLw4e2YMG/ 560 | TL2k8E/rrVejiUaPG98xRGkC7Peq7ltPtv0SzhnTAcHuQlC0zy7KsgiRCyddGWls 561 | /1xIPHIJK5qPDYtZwMeq9i8i2LAmmg5quKbdcFf5jV+isQuNB/SzYidqdHnnKDV8 562 | ju0bE+7S4ufKoUSzTD4HANXrQvudJwvqmjyBu9RsWTuaeKpUoXXGjsojIwFj3xcY 563 | IAsRrlsA1mdtKY/GCG9tIvhKnZ1WR6yqMfSo0C8fu0hX3S404Yn0AXIAYRnQsxeM 564 | UvaVhKmYbwKpARmI3AeAyL/wt9loOwvYrCKpE+m39Z9j9XppTDebr6W1MGiWNxJE 565 | oZP5etbRkclzu8oNY9jMP7AnzdFbV8dU65J8XdsyVL9eXUZJd9DOOzwRPIm1CPrO 566 | xmNV6SXCULHCYQKDofGtbZqiO6UR9DKIwISOVRZbfUsu6oAI6Sla+CFvvLkCDQRS 567 | 3XLqARAAp41UUmwfsYgIcW8PcLWZpzEyTj0HwpUfv5UyXt3zKqaPTVUPFpLErlpH 568 | x/3wI0buFN4rlGKYq7JaWD4hEBuTBqXL7g117CRcpA3NVCiGL1jzPzz6Vnbjcp46 569 | euFmiG6LnMODzmTMNHwqSSTAS4ILk/fFcc1MVbTo74RsDPyMvQXK73zZTxlBpdXy 570 | TCSVmFSmcLHZlH7ogeV3Vtn93N51eKksnyNsuOwEjDiCunfhVo5r/LTNFJnhiKv9 571 | 1etpm6FX3fWrZDtXxmUe2e7BIacDKm7OrlUMwGVDgTXQzyqgCbM1Wx9HFzI/oHON 572 | j3cokmsM2fIOwojjpsAxxw5m8po9u3ETOTUBliwAGfiokK7ov40yulee0Zv4TwDI 573 | ASskNXJr7oXWJkIZhAijuL2hpDt1aBRdRfBh9JUXZMPHDtgHDSXupiTyRI8yGs3Q 574 | 392VLRsk3Ool5axEjc0umH7tKsYwHO4FdZPUpDr2KlAa9QU4T/NPDVKpr1ku4iWS 575 | 6rEgZvgPd3Cubd8maP9A0Y3kWRRA/3qcDnPih5WttBDXBTQoWrkTSRKPEWAEMs2r 576 | LpF6FL4Q2LG2JRMTMHj/cfAN/P3ZrSciCsVyMrgFbYcHdhDirxYdRRiuEsvDnnk4 577 | oVtvJK0/YWzaNf4IxSNKBbhnVESJ5omJ0KgF84wA5wZI8RkPqbkAEQEAAYkCJQQY 578 | AQoADwUCUt1y6gIbDAUJEswDAAAKCRAZmiWlf56L+oF6D/42ISPghU5WADEL6NOs 579 | iSFyRxvDUhayKjasZLGOLobvV3FCJ5W0cF6JvdONx+sLwXWIgnH22iGcsyH1fTH1 580 | ikFOUmdsKs9cE3J9psUH3JFjTTApWzV1nLcFbeNAIXn+6XaAWz9Uqyff3JxN0AyW 581 | K4TQLLTFsWzc2r/oEBKXUuHs5r2omX894pIbCRVuwHMeO3R7FX7kCUEEL92MaKFW 582 | 84ny5pQTAGAabg0fm1yeOWrKupwiurQOkAzNAiYs3trd8pJtHkx2Hft9hQRVo1J7 583 | Kn7eFIuYFnu3B8L+WZhQbG8KOX7r5ara3fpTYECAC5pNw3p1xj7BFV+7sM4G3wzc 584 | yDA79CAQ6y/I1FPBspoTKxRzpOIa36NTZpMiH0ERY9eJDUL9pQnjlE5/JlcGWbT5 585 | v14QJm3+fdOkEJ4x+MNMUdSiuolP9sVKyBNJkwYRz9QKI1kgVPzTgPM0HyXWMgnL 586 | j0/cLPxTEbpbNXN95lNA7rFM/hkYAIhXtsfvNtIhZ3DtzGKK7aRv+iKJKKapDi4n 587 | TLCB+uwPy7++koFMI5yDZC1wHh95iLwGgmOOyW3P6IhGN5SMl6QSoBqDJpzNdGy8 588 | Q/3f/q4Uxo1JH2DZOqoAseqXRFkxF0ByIpovsV0TQmpDZ7x6wHShKzA/LTfcITEO 589 | IPN3AH6hvZ8gKAutZO+TjtBf+JkCDQRS8rt8ARAAtc6u/t5Jh8tYRfWgq+OzwjwZ 590 | JGwA8zQBKeSgdP/aeAzpk3A51uQEFv64fcB/IpQ4WSjzx/8TqpSmtF0p+ybHE/yq 591 | YoMHmLvVr6Zwdp5z6V9o+7QQ/8fP47aK6Cca54vrOYK2aNDHd6uTLHjzsBXSQq6/ 592 | Hs0B2gQFVYPCzVTHGN+x9/lGzfzcwvIlGJeflTQN3mqf2x0YuoFitkRQAy/xAksX 593 | MXL6EwQsKP39lF7PFRsttvZRxmHOCu1VnCFDnUnVFSdjAP+R4s4BFLXvRiwXw6US 594 | puffTjMIkE26OCEZXlD2E+v9GpJbIfZrT853jVl2tahCeXovlpO2Yl7dLowC/WJ5 595 | OQxqOqggVlO4TL+yjnhI4R69NSVaH/ot4NvOilEGJNDenaOBPYROY0cCUKfudLI/ 596 | ROci49PqRFkNMf/tgt7rn/aLa70VmJOlwu/fWbyNwvtLTxA7E2pIE0CZunHlLVFx 597 | rSv0zwHp+4cADOMfVV40HO3QmQWYo6PBkdYjGpLUKcrL2tDfXsDfOvhYnXV7cmne 598 | iuFnpWUEEa4+bYs3oYy05tfBtRr/0eAQjCBYGxPJ2gQQftX3Cw3O0oiucFFr9NGA 599 | 0YpV/M44f9/9HQWcGOwJc/Y7cF0QNU245a+RUnOcecKAq0l+XMgsFnzKzgzfrqE0 600 | p+AMLfE9NaYqEoB1adcAEQEAAbQoa2V5YmFzZS5pby9tYXggKHYwLjAuMSkgPG1h 601 | eEBrZXliYXNlLmlvPokCMwQQAQoAHQUCUvK7fAIbLwUJEswDAAMLCQcDFQoIAh4B 602 | AheAAAoJEGBSsq0xpmMcZr4QAI/yEdCksc3SFMFMIrBwJJXdPpYmt3g7JoYyovJ1 603 | I/6QUH4LcR3EBd0mmYnumGyOfy1kfkpiXYlOykRMyuUORqBw9siqCbm6FYY4qAq9 604 | flYbj7CWf+1OHT7xYdGM79DDvX/SrpI6byChOXZ89nI3gKMakDEoemiRlWivOLeO 605 | kucfcGRpG5Tut12Ctgyg2XB9Oj5gz9jBgsFHUYwk2YacX/L/BPPFGqxnRivYq4Ji 606 | 0Aujv37Ve83hkzbLdtTT3VRbKp+ERRAQYCwwD4vvSPFhnVRNw/b7Xd0UjDimJVDS 607 | hdJGjFTd8s/5jHpntbECLcSukddL8XS4HzHD/j9JjcA3xo0dbD5UARFTwS+ALnRt 608 | HUFdre/KeVRFt31bLmUscpPR1mHYE9gdq88A9w8lOUrZo9fOTHBksIjvZj096lWR 609 | beX9VG2O1JYDnofQjO75hM+EXDuuTrKnBHyA73xWlHAAn+pevDXIS53dLnzr3ST8 610 | CiyJuBMNzjVC/PlwCFoGGeoRQffM+bZ3XyWL+b9Wywk0UnP6XvuRHui9g5zL8abU 611 | KBaa1ILoXGtlv68fvEqFvPgmyQluHxOK5lHcemyIYjOGB86WUGxR6KhztwvuUnYF 612 | QeMKcQ8Yu5Qh4XAruaK8mX3QhVchCaWpAoqCcUIS0j9cTO0oRtIjZlF/kyn/kPtY 613 | ADpXuQENBFLyu3wBCADMNxfQlPyGgnjp3jAIhwFJbK5DIJgZOdCh/IdtyPsyyvo8 614 | S1iraIbJhp9I53M59KeLohHYakROOuE/3pkhRxGwEHfK/W4HCRNN94SidxV65tR5 615 | wHgnhcFTcYktnFkYJ1D+sNe2F5NmXatl/bgz8ilIadqUSpYnPxZIRKc9ZmpMyr5u 616 | pVFKbavf8VM2KV1A6Nsaqk+HwH9A8L9IeLpuY3fO4q12dU8XNEDAXhcllrir5py+ 617 | W3QhlnnS8k6d1Cwl3sbruxnewHQ1FOYnAy0nvx6crLf0rPVLOL02buoYirCwZ60f 618 | zv+FURJ55hAJiTJsdaWb2BUlaFw81gYqxuC4THALABEBAAGJA0QEGAEKAA8FAlLy 619 | u3wFCQHhM4ACGy4BKQkQYFKyrTGmYxzAXSAEGQEKAAYFAlLyu3wACgkQmAo/DQH+ 620 | BN+M+wf/V4/hBFm59NZdnLzzDJp7B+bxWKh5G75PU/AlxP0HibsjIJXT49Cyhhwn 621 | AD+6VJVMS5QDDQCRPDXfnr812jbE6oxd2pInWZ6oyl/1EaI9XZUVR3re7tNbAI8z 622 | WIjGt8rFkQehQ60LKd+os9ZEfpRlaYnmTZ/IAvspUM9PUlRxU62bselxxyXttxqx 623 | WTpo8iZ4kw30P6jbZ6ADiv09ZR14HnOpcQfa3GodYIASoYnq//rNfXS1J8MExtes 624 | /X+XbRg+5OLy/iGEpGZES0zVt3ioiESXe37YR0bFTjw4TggMz9m/NqeyokexLazA 625 | xYh6V8SnAUFY3jrHLLO6FLq1+5YShaf4D/9Afpnb0j1xeWsZKyE68Jo5Fu+tMiYL 626 | NuKiprnZ1KorDQULZFmHjVFvv8LN8y9rh/9ccCkKUY0XbcQ4SmkRGUsyFBsSlVtc 627 | 7bsJ2clBwwQ4rc0kql40X9GOOs7E2iulcabx9g0K15DO7A5qnhyxntJhB6864Zu9 628 | 9WU2gBRKffoVFjnZ1BxozF+C41lcEeOKlvYGmZCRKViaQrRIQoLs/y71aEHF9M28 629 | fZHvEpkRMjcncL1Ra1/L/C02DhVI6WQzJmmlfyhfRP+RC+ZtGCmEd6V60cpCZyVX 630 | kaqJoac9b3T/LbGKivPpD0iQhQ5+W4apQZxS7SVmhQCgLHrBQuvwtQ1Crh4Rh/Xi 631 | kQxDoS4zV9hXOw+bfjp4vQFcidekJLt7IoKNxPCMp16GxIGMYMXcwwCiroMZ18fo 632 | xxNOM7dFlqMWSffRlckbmcAaCdLVcVhGiONE89M6nKoBcNX9feckGPTMrEIdj6/2 633 | ZXnOGN3b1uPsDCSp4o8CqAhR0a/RW0KILz58igBV9cWDMa0vb9Xlk0PQze27EvOn 634 | dgb6qDiF/k4e/+6AVrw1ziStDRo0PJuLi9geWtwk3QAZWHSm+of3xEjT5Nwze8vI 635 | aKzKOi2wsrSs7bQSnD2c41aKq5TPkySxJaXv4huEXsjM3x8AcTNzkVhZ3LvO/hx/ 636 | /6eFUsNDH74yM5kCDQRS+9qAARAA2tKIVfuMz0XXZx1grJWEJRsPWFPVirPr8+yj 637 | IyAajQIXj0uZuQ20ZIuGHwZENl9Z0c/ooKMDdaA9E9/tsNyfJLzUeNxKmDdYHtg6 638 | Ikej7XMLfecJbejbw3zv5vl6kfauJO6+9/PMPbrAfMKXFqv6X8IHVa1uEUoDT8Zh 639 | 2azfuT7hALTFTSk1Pn9tbPPsv8j04a7EhyglfKsugxQRcWTkeDVqLqB70A1xna0j 640 | sWtWE/EwOaEchdL95xGxEO8K/P4sHBgqDopNc0PGMVKoCRqsMpUqsPPEeH2SALul 641 | MzZkITaJVwOOs9PCe8d9QTYMg46Wl8NAwyHLV8+jAim+wWTcfAKOSt2ymlt6wMYO 642 | YDe11TgelblMQAQ7h665tPCyz39UDIQaogkTgKRZTd/yhz3HydX4Cz7DjgCVfEFT 643 | m+4HSxqCJsAN71TIzqLvoCRyA1TWYnhmjQYvQcynzOtTJP6CxkSPhBPKjkReDVjT 644 | aR+6eaRA3m/byvS+Wk35oOiyD3ju1Gi8sIdRfnL/eFhMp+kkHEs3foQCRXgsXvX1 645 | IweBwJbAvTcnoNkvzqjdPwF1McWd/zl+HeQUiiQDEToQGHDcgQhTcgl3HOUjZeAl 646 | akQBvLKddW8oy47DM8ZyRh3AH9NfZdCeuWIFRAi2g4+s78l1y7nVLLOlAJtVHZFT 647 | 9O0t7JkAEQEAAbQ2a2V5YmFzZS5pby9jYXBuZGVzaWduICh2MC4wLjEpIDxjYXBu 648 | ZGVzaWduQGtleWJhc2UuaW8+iQIzBBABCgAdBQJS+9qAAhsvBQkSzAMAAwsJBwMV 649 | CggCHgECF4AACgkQWwlJSLERUfIFjQ//Wg1uky7tgjitBQH8V0Yf2fyRE+rKmh1N 650 | 8NlSMrBuBUERf3IvLjkwk8CTtoOgF6WJk6NXHOUboGtVL2JvtJ48CTYntdEeFdN3 651 | 678hfiur/xnbMRXoV5IHq9Ph5n5h1xOysRLVPkdxJfNBgDRVoyx1Tqo0Vqj5a5il 652 | N9rT+1z9Me6VX0QbTRvsX3dUWOX6PXnSgoKqEzsYPNsBRwxGz4anR34DBXcRVrpM 653 | dHeCI6Zhja2or0nPCKMBtorkoSqgw5AgIjY+ZmPEYI9NaCdkl29CpjvMg9Ii40x4 654 | g5/jQARFaLZtJ8Ngd299v5RVbQ2CpysHdo7kzaXoFEEEF9yF5Y1BKsMSbmv/q0XD 655 | YeeqqkqcZCEWh3eZc5ns12ju7E3z8EoVejaTB7aB6jjuYWLLhgkzUZfcGknrxPCG 656 | W8TI1aP/eVaWYJFL18ay2vZzqCRrGqv1d0aAF1rbm/CSZLz7+grhhuW/20Q2x12D 657 | s9h/nKyeIFcy2TnCbZdwcayP8nj9Z2bRFsRjouSMO3erOzICRmQ7QsZcXOftIt5g 658 | 3DSBgoIYRpRIshyHjgJbM8dzHaB0lawM3TSA9pYmSwdc6w7xlpQT8HGz4YK0Z7wU 659 | ZOJNmlHZyg3TruVdR7As3NKhjCdBR7wU54n8SWlDlAX4a5ItTzt/ggiiMGyleO1X 660 | 893VaE3Iko+5AQ0EUvvagAEIANvCWawMogfHD8xZECFefVfVo54bGvG7ZT0fGCN7 661 | FR6jN7U58x+HppL2dK2BqYYOOzzsZGSnXBLq5REt0qL0Pap8oSe3wenn1MFGRTdY 662 | qlJTu0vsxgmbvVQoTq7RGl24gKsTIDyHlfQSs9fgKn4muR1RyZeNGXO8RCTlN50Q 663 | VRuI+y7kuwgG3waIsRUO8D7N3W3aP7HLzzaWVh8CQZS9nBI7D/wzwalBAElv5I0d 664 | 4tNUurS+gOBgO8/rvFhZxHPEpOKPGKfDlQqw9iR5jOgn6NzfTsHDwr8UOhadNzEo 665 | AjdrCyn1Yj8gZSOjnx3LfDdNHN5nLZ0m1klMsfkVkAInSZ0AEQEAAYkDRAQYAQoA 666 | DwUCUvvagAUJAeEzgAIbLgEpCRBbCUlIsRFR8sBdIAQZAQoABgUCUvvagAAKCRBL 667 | q/m8CroyO2AXB/4yFGAhmoFq3qYeWC4MX3FJPmXp8BTBzYeK2atATyZevikVUE7d 668 | HAm+Se4NlUnjEpfbuNE0OTIevNs6SFbIf1xXXZyOU5NvQvSKktVEQIVgay92YW+r 669 | w1D8DZWwaGY2t6RvP/6hjvehwiSo9PGCzP5gVB9elAtNx1pWL6IFmNnbOKHBgiWC 670 | jVtI4mpJrEl/fJjAZtdiprs2xujOTyk2UBbVQNHiqSIAUIEN8PNF0H5L0AjxucgD 671 | dqe6MuhNSiqejEdfdZSDIBu4OF3J+zEz+bTZ3DPTnXJ77PGERMINCcTagmFc6ovc 672 | /uoxmzCoT+zbEfdT53wLiDBamVkLdKl717GmaTkQAK6UD+7KgxyJcNiTV/1pjX5W 673 | 6ZRqcsiF8uIuxU/DWp+QQbHLoLvbggJZqJt0RrLw1AUe3Li6kDoMChXXYMPBS2xL 674 | f+omhD36AXhpeqJQKoRJ2ANV5VyWuoRCkIokxbeAcqR53lPv4vx05tVEJLHet/9X 675 | ETrsdVlcDvxDTc03+viC8ELOQRZ9RYTMWVNiJd30WV8XoDPxb/ZSEMizCW4o8Xo8 676 | 6xkxWk8XWxQtR1fPcH2Z3GQPSiNZgOXIMT9X9xa0oE7ZhxtINp7LhN5Y+BAjbxsO 677 | qdHbQ3fYMjAiDC/X8Vm/fXJCc7yKB576MJOrkxbQ8MKnOI3P45XIVtg2HPgb3Zpy 678 | Pg0jFv2mSjeK1LOmRi8pGy1rWdD3nXI7hvfCyiihZVXVdHcB+dyPIiI42j8HBn6X 679 | C1QJUgv60GOIM+DbBwfcmQI2MRtUHytpPWVQc199qpVH7P1QzrKItTXN0q5QsEiV 680 | 1Xsu6cqfLaI4jr30WNxqhfdy11N2OwB8+1C/TOdTBUVSjxieHo1H1AADBZDiwFnG 681 | /xekL0nxnQW5l5BCiXB6Pk4pCVpkHikq0at6byMpObP9NU9/YmRAUsprVNcrtojj 682 | +mQbuq3M7n3jaCv18hKcK+23dolzlhQjqD/QvHiRgJoJUrnAfqtHsDdkyQDTwLJo 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 | --------------------------------------------------------------------------------