keybase / kbpgp

OpenPGP (RFC4880) Implementation in IcedCoffeeScript
https://keybase.io/kbpgp
BSD 3-Clause "New" or "Revised" License
534 stars 74 forks source link

Clearsigning with kbpgp #109

Open iamtedko opened 8 years ago

iamtedko commented 8 years ago

Hi,

Looking through the documentation, I didn't see anything that would you clearsign a message. However, I know that it works on the command-line client. Would it be possible to provide some more information about that?

Thanks!

malgorithms commented 8 years ago

hi @iamtedko - in our email chat you mentioned hitting an error with some sample code. Can you post your attempt and the error?

iamtedko commented 8 years ago

Here's the error I keep getting:

/node_modules/kbpgp/lib/openpgp/clearsign.js:168 unhashed_subpackets: [new Issuer(this.signing_key.get_key_id())] ^

TypeError: this.signing_key.get_key_id is not a function at ClearSigner._sign_msg (/node_modules/kbpgp/lib/openpgp/clearsign.js:168:59) at iced.Deferrals.parent (/node_modules/kbpgp/lib/openpgp/clearsign.js:233:19) at /node_modules/kbpgp/lib/openpgp/clearsign.js:242:13 at Deferrals.exports.Deferrals.Deferrals._call (/node_modules/kbpgp/node_modules/iced-runtime/lib/runtime.js:86:16) at /node_modules/kbpgp/node_modules/iced-runtime/lib/runtime.js:98:26 at exports.trampoline.trampoline (/node_modules/kbpgp/node_modules/iced-runtime/lib/runtime.js:64:14) at Deferrals.exports.Deferrals.Deferrals._fulfill (/node_modules/kbpgp/node_modules/iced-runtime/lib/runtime.js:96:16) at iced.Deferrals.parent (/node_modules/kbpgp/lib/openpgp/clearsign.js:223:28) at ClearSigner.run (/node_modules/kbpgp/lib/openpgp/clearsign.js:225:15) at /node_modules/kbpgp/lib/openpgp/clearsign.js:449:11

Is this caused by passing in a wrong "signing_key" object?

malgorithms commented 8 years ago

hey @iamtedko - thanks for posting. I think this'll require a response from @maxtaco but I know he's on vacation this week... so I'd expect next week before he can reply. For what it's worth, I tried too and got the same error, so I think we're just doing it wrong.

Based on the previously closed issue ( https://github.com/keybase/kbpgp/issues/32 ) I thought this code would work (coffeescript):

kbpgp = require 'kbpgp'

F = kbpgp.const.openpgp

opts =
  userid:        "User McTester (Born 1979) <user@example.com>"
  primary:
    nbits:       1024 # for testing...
    flags:       F.certify_keys | F.sign_data | F.auth | F.encrypt_comm | F.encrypt_storage
    expire_in:   0 # never expire
  subkeys: [
    {
      nbits:     1024
      flags:     F.sign_data
      expire_in: 86400 * 365 * 8 # 8 years
    }
    {
      nbits:     1024
      flags:     F.encrypt_comm | F.encrypt_storage
      expire_in: 86400 * 365 * 2
    }
  ]

kbpgp.KeyManager.generate opts, (err, alice) ->
  # and sign alice's subkeys
  if (not err) then alice.sign {}, (err) ->
    console.log "Made a nice test key for alice..."
    kbpgp.clearsign { msg : "hi", signing_key: alice }, (err, msg) ->
      console.log err, msg
malgorithms commented 8 years ago

Ok, I wouldn't trust this without @maxtaco 's seal of approval, but poking into the code, I think this is what you want:

kbpgp.KeyManager.generate opts, (err, alice) ->
  # and sign alice's subkeys
  if (not err) then alice.sign {}, (err) ->
    console.log "Made a nice test key for alice..."

    # figure out which of her keys is best for signing!
    # clearsign doesn't do this automatically.

    flags = F.key_flags.sign_data
    signing_key = alice.find_best_pgp_key(flags)
    kbpgp.clearsign { msg : "hi", signing_key}, (err, msg) ->
      console.log msg

with other kbpgp operations this step is done automatically with the keymanager, but that hasn't been implemented for clearsigning yet.

trogau commented 8 years ago

@malgorithms, just checking to see if I'm reading your last comment correctly: do you mean that eventually there'll be a box method created to do clearsigning (similar to how signing works now)?

poluxus commented 7 years ago

Hi. I would like to know if there is any improvement on the subject? It's hard to find information about kbpgp and clearsign

securityvoid commented 7 years ago

I've got something which appears to be running and is identified as a signature, GPG is telling me its an invalid signature:

module.exports.signData = function(data, privKey, passphrase){
    "use strict";
    var deferred = q.defer();

    getKey(privKey, passphrase).then(function(key) {
        var params = {
            msg: data,
            signing_key: key.find_signing_pgp_key()
        };

        kbpgp.clearsign(params, function (err, msg) {
            if (err) {
                deferred.reject(err);
            } else {
                var signature = msg.substring(msg.indexOf('-----BEGIN PGP SIGNATURE-----'));
                deferred.resolve(signature);
            }

        });
    });
    return deferred.promise;
}

function getKey(privKey, passphrase){
    "use strict";
    var deferred = q.defer();
    kbpgp.KeyManager.import_from_armored_pgp({
        armored: privKey
    }, function(error, key) {
        if(error)
            deferred.reject(error);
        else {
            if (key.is_pgp_locked()) {
                key.unlock_pgp({
                    passphrase: passphrase
                }, function(err) {
                    if(err)
                        deferred.reject(err)
                    else
                        deferred.resolve(key);
                });
            } else
                deferred.resolve(key);
        }
    });
    return deferred.promise;
}

Double-checking my work now to make sure I didn't somehow miss something.

This is what I'm running it with:

  var fs         = require('fs');
    var lib_crypto = require('../app-shared-libraries/cryptography.js');

    var key = fs.readFileSync('../.Research/SampleFiles/keys/privKey.pgp', 'utf8');
    var passphrase = fs.readFileSync('../.Research/SampleFiles/keys/passphrase.txt', 'utf8');
    var releaseFile = fs.readFileSync('../.Research/SampleFiles/Release.txt', 'utf8');

    lib_crypto.signData(releaseFile, key, passphrase).then(function(answer1){
        "use strict";
        fs.writeFileSync('../.Research/SampleFiles/Release.txt.asc', answer1);
    }, function(error) {
        "use strict";
        console.log(error.message, error.stack);
    });

And this is how I'm validating and it seems to be failing:

gpg --verify Release.txt.asc
gpg: assuming signed data in `Release.txt'
gpg: Signature made Fri Jun 30 22:46:56 2017 EDT using RSA key ID 87DD0060
gpg: BAD signature from "Joe Bob <joe@bob.com>"
securityvoid commented 7 years ago

Okay, I think I figured it out. There appears to be a bug in the way the final line-feed is handled. Currently it appears that the final line-feed is removed before the signature is generated. This makes the signature properly validate when its a composite message, but not when you try and use the signature to validate the original file. If you simple remove the trailing line-feed from the original file, everything works fine.

Based on the following document: https://superuser.com/questions/933333/how-to-create-a-single-gnupg-signature-which-works-on-both-lf-and-cr-lf-versions

It appears the line-feeds are only supposed to be messed to if the signature is in "text-mode" which is a sigtype of 0x01. Even then it looks like the substitution is typically changing \n to \r\n, but in any case the sigtype being generated is of 0x00 and not the textmode's 0x01.

I'll open a new issue with those repo-steps, but in the meantime anyone who is looking to clear-sign should be able to use the sample-code above. As long as they either have a file without an extra line-feed at the end, or don't try to detach the signature it should be fine.