jstedfast / MimeKit

A .NET MIME creation and parser library with support for S/MIME, PGP, DKIM, TNEF and Unix mbox spools.
http://www.mimekit.net
MIT License
1.83k stars 372 forks source link

Howto import keys into GnuPGContext using ImportSecretKeys? #220

Closed polterguy closed 8 years ago

polterguy commented 8 years ago

Howdy, I'm fiddling around with trying to create a key pair using BouncyCastle, and figured I'd use the ImportSecretKeys method on OpenPGPContext to import my keys, but it doesn't work. It doesn't throw an error or anything, but when I check out my keys in GPG Keychain (MAC OS X), the keys don't show up ...?

Here is my code ...

// Creating our keypair
IAsymmetricCipherKeyPairGenerator kpg = new RsaKeyPairGenerator ();
kpg.Init (new RsaKeyGenerationParameters (BigInteger.ValueOf(0x13), new SecureRandom (), 1024, 8));
AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();

// Importing keypair to GnuPG
using (var ctx = new GnuPrivacyContext ()) {

    // Serializing key(s) to MemoryStream
    using (var memStreamPrivate = new MemoryStream ()) {
        PgpSecretKey secretKey = new PgpSecretKey (
            PgpSignature.DefaultCertification,
            PublicKeyAlgorithmTag.RsaGeneral,
            kp.Public,
            kp.Private,
            DateTime.Now,
            "foo@bar.com",
            SymmetricKeyAlgorithmTag.Cast5,
            "password".ToCharArray (),
            null,
            null,
            new SecureRandom());
        secretKey.Encode (memStreamPrivate);
        ctx.ImportSecretKeys (memStreamPrivate);
    }
}

Is there something I have misunderstood ...? (which I assume btw)

Basically, what I am trying to achieve, is to create a private/public OpenPGP keypair, and import it into my Keychain ...

jstedfast commented 8 years ago

You might just need to memStreamPrivate.Position = 0; before calling ctx.ImportSecretKeys()

jstedfast commented 8 years ago

Here's what you need to do:

PgpSecretKey secretKey = new PgpSecretKey (
            PgpSignature.DefaultCertification,
            PublicKeyAlgorithmTag.RsaGeneral,
            kp.Public,
            kp.Private,
            DateTime.Now,
            "foo@bar.com",
            SymmetricKeyAlgorithmTag.Cast5,
            "password".ToCharArray (),
            null,
            null,
            new SecureRandom());
var ring = new PgpSecretKeyRing (secretKey.GetEncoded ());
var bundle = new PgpSecretKeyRingBundle (new [] { ring });

using (var armored = new ArmoredOutputStream (mem)) {
    bundle.Encode (armored);
    armored.Flush ();
}

mem.Position = 0;

ctx.ImportSecretKeys (mem);
jstedfast commented 8 years ago

I probably should have named this ImportSecretKeyringBundle(). I'll probably rename it for 2.0

polterguy commented 8 years ago

Thx, but it still doesn't work. Here's my entire code

// Creating our keypair
IAsymmetricCipherKeyPairGenerator kpg = new RsaKeyPairGenerator ();
kpg.Init (new RsaKeyGenerationParameters (BigInteger.ValueOf(0x13), new SecureRandom (), 1024, 8));
AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();

// Importing keypair to GnuPG
using (var ctx = new GnuPrivacyContext ()) {

    // Serializing key(s) to MemoryStream
    using (var memStreamPrivate = new MemoryStream ()) {
        PgpSecretKey secretKey = new PgpSecretKey (
            PgpSignature.DefaultCertification,
            PublicKeyAlgorithmTag.RsaGeneral,
            kp.Public,
            kp.Private,
            DateTime.Now,
            "foo@bar.com",
            SymmetricKeyAlgorithmTag.Cast5,
            "password".ToCharArray (),
            null,
            null,
            new SecureRandom());
        var ring = new PgpSecretKeyRing (secretKey.GetEncoded ());
        var bundle = new PgpSecretKeyRingBundle (new [] { ring });

        using (var armored = new ArmoredOutputStream (memStreamPrivate)) {
            bundle.Encode (armored);
            armored.Flush ();
        }
        memStreamPrivate.Position = 0;
        ctx.ImportSecretKeys (memStreamPrivate);
    }
}

After executing the above, I open my GPG Keychain, and look for my keys. No new keys are shown though ...?

polterguy commented 8 years ago

PS, I am on a Mac OS X, using GPG Tools ... And code is running in ASP.NET app, on top of xsp4 ...

jstedfast commented 8 years ago

Hmmm, that should have worked... I'll look more today.

polterguy commented 8 years ago

Thx, you're a champ :)

PS! No exceptions occurs ... Suggestion, when you invoke "Import", and nothing is imported, maybe it should throw exception or something ...? I've looked through your code, seeing that you check the number of keys that was successfully imported, and only if this is more than "0", you actually save the keyring. Maybe you should have an else here, or something, that does "throw new ArgumentException", or something ...?

jstedfast commented 8 years ago

Yea, I was thinking about that yesterday.

jstedfast commented 8 years ago

Okay, so the thing is that the secret key is being imported into secring.gpg, but MimeKit does not add an entry to trustdb.gpg. I'm not sure what the format for that file is, so I'm not sure how to add records to it.

polterguy commented 8 years ago

Hmm, a quick search returned this ...

http://unix.stackexchange.com/questions/110500/how-to-regenerate-etc-apt-trustdb-gpg-on-debian

jstedfast commented 8 years ago

Unfortunately that doesn't help because that's for the system gpg installation where the solution is to just rm -rf the gpg keys and refetch them.

It looks like someone is working on implementing support for gpg's trustdb in the java version of bouncy castle, so it might be possible to port that logic over to c# and get it into c# bouncy castle, but that will probably take a while.

polterguy commented 8 years ago

I tried this in a terminal;

rm trustdb.gpg
gpg2 --import-ownertrust

It says; "trustdb created" afterwards. When I open my GPGTools Keychain afterwards, all my old keys are there, without any trust besides the default. None of my generated keys can be found though. I am not sure, but I think trustdb.gpg is only for associating trust with keys, and that you don't really need to fiddle with it in any ways ...?

I am not 100% sure, since it is difficult for me to debug your code, since I am using the nuget version, but I think there is something "fishy" in your "SaveSecretKeyRingBundle" method ...?

polterguy commented 8 years ago

PS! If I delete the "trustdb.gpg" file, for then to run my code, for then to open GPGTools Keychain, my "trustdb.gpg" file is re-created, but none of my keys can be found. I even tried to delete my "secring.gpg" file, for then to rename "secring.gpg~" to "secring.gpg", without success ...?

I am not sure, but I don't think the trustdb file is related to the issue ...?

polterguy commented 8 years ago

For the record, if I run this code, and open the resulting "foo.asc" in GPGTools, it will import my keys ...

// Creating our keypair
IAsymmetricCipherKeyPairGenerator kpg = new RsaKeyPairGenerator ();
kpg.Init (new RsaKeyGenerationParameters (BigInteger.ValueOf(0x10001), new SecureRandom (), 2048, 12));
AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();

// Importing keypair to GnuPG
using (var ctx = new GnuPrivacyContext ()) {

    // Serializing key(s) to MemoryStream
    using (var memStreamPrivate = new MemoryStream ()) {

        // Creating secret PGP key
        PgpSecretKey secretKey = new PgpSecretKey (
            PgpSignature.DefaultCertification,
            PublicKeyAlgorithmTag.RsaGeneral,
            kp.Public,
            kp.Private,
            DateTime.Now,
            "foo2@gmail.com",
            SymmetricKeyAlgorithmTag.Cast5,
            "$sdfSDFG543y896_-+=".ToCharArray (),
            null,
            null,
            new SecureRandom());
        using (var armored = new ArmoredOutputStream (memStreamPrivate)) {
            secretKey.Encode (armored);
            armored.Flush ();
        }
        memStreamPrivate.Position = 0;
        using (FileStream stream = File.OpenWrite ("/Users/thomashansen/Documents/foo.asc")) {
            memStreamPrivate.CopyTo (stream);
        }
    }
}

But when I wrap my key into a keyring/bundle, no keys are imported ...

jstedfast commented 8 years ago

Well, I wouldn't expect it to work because gpg probably doesn't know how to import a bundle, it only knows how to import a single key.

polterguy commented 8 years ago

BTW, not sure if this was lost in "translation", but I am almost certain that the "trustdb.pgp" file has nothing to do with this ... See above comments ...

jstedfast commented 8 years ago

I'm pretty sure it works because the unit tests depend on it working... it's possible that the output of saving the bundle isn't completely readable by gpg, but I'm not sure

polterguy commented 8 years ago

Also, when I run "gpg2 --list-secret-keys" through shell, it actually show my keys. Only in the GUI of GPGTools my keys don't show. I've tried to use an "invisible key" to encrypt though, without success ...

jstedfast commented 8 years ago

Heh, I was just about to say I wrote a little unit test and then checked to see if gpg2 could list the secret keys:

[fejj@localhost gpghome]$ cp ../UnitTests/bin/Debug/secring.gpg .
overwrite ./secring.gpg? (y/n [n]) y
[fejj@localhost gpghome]$ cp ../UnitTests/bin/Debug/pubring.gpg .
overwrite ./pubring.gpg? (y/n [n]) y
[fejj@localhost gpghome]$ rm -f trustdb.gpg 
[fejj@localhost gpghome]$ GNUPGHOME=. gpg2 --list-secret-keys
gpg: WARNING: unsafe permissions on homedir `.'
gpg: ./trustdb.gpg: trustdb created
./secring.gpg
-------------
sec   1024R/B6FB2584 2016-01-21
uid                  foo@bar.com

sec   2048R/AB0821A2 2013-11-03
uid                  MimeKit UnitTests <mimekit@example.com>
ssb   2048R/AA3EA3BA 2013-11-03

So it was definitely imported and written out just fine.

You know what the problem might be? You imported a secret key, but not a public key.

polterguy commented 8 years ago

I know, I started slowly realising what was wrong myself ...!! :D Obviously PGPTools won't show a key when it doesn't have a public key ... So then comes the question of how to import the public parts ...? I am assuming "Import" on OpenPGPContext, but importing WHAT ...?

BTW, may I suggest a little "API change". Instead of supplying the stream, how about supplying the "PgpSecretKeyRingBundle" instead, and then have a method that extracts both public and private ...? :)

Maybe mark the current one as "obsolete" ...?

polterguy commented 8 years ago

Puuh, OK, got it working. I'll write up a blog post, trying to explain a tiny example of what I did. But basically, this worked .... :)

// Creating our keypair
IAsymmetricCipherKeyPairGenerator kpg = new RsaKeyPairGenerator ();
kpg.Init (new RsaKeyGenerationParameters (BigInteger.ValueOf(0x10001), new SecureRandom (), 1024, 12));
AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();

// Importing keypair to GnuPG
using (var ctx = new GnuPrivacyContext ()) {

    // Serializing key(s) to MemoryStream
    using (var memStreamPrivate = new MemoryStream ()) {

        // Creating secret PGP key
        PgpSecretKey secretKey = new PgpSecretKey (
            PgpSignature.DefaultCertification,
            PublicKeyAlgorithmTag.RsaGeneral,
            kp.Public,
            kp.Private,
            DateTime.Now.AddYears (3),
            "Foo Hansen <foo22@gmail.com>",
            SymmetricKeyAlgorithmTag.Cast5,
            "foobar".ToCharArray (),
            null,
            null,
            new SecureRandom());
        var ring = new PgpSecretKeyRing (secretKey.GetEncoded ());
        var bundle = new PgpSecretKeyRingBundle (new [] { ring });

        using (var armored = new ArmoredOutputStream (memStreamPrivate)) {
            bundle.Encode (armored);
            armored.Flush ();
            memStreamPrivate.Flush ();
        }
        memStreamPrivate.Position = 0;
        ctx.ImportSecretKeys (memStreamPrivate);

        var ring2 = new PgpPublicKeyRing (secretKey.PublicKey.GetEncoded ());
        var bundle2 = new PgpPublicKeyRingBundle (new [] { ring2 });

        using (var str2 = new MemoryStream ()) {
            using (var armored2 = new ArmoredOutputStream (str2)) {
                bundle2.Encode (armored2);
                armored2.Flush ();
                str2.Flush ();
            }
            str2.Position = 0;
            ctx.Import (str2);
        }
    }
}

The weird part is that unless I have the last "Import" statement AFTER the disposing of the armored2 stream, then it won't work ...? :P

Anyway, thx. I'll write up a blog post about this, such that hopefully the ones coming after me won't have to fiddle as much with it as I did.

PS! Still think the API change, having OpenPGPContext take a "PgpPublicKeyRingBundle" and a "PgpSecretKeyRingBundle" might be a good idea. The "stream" parameter is basically impossible to guess what it should contain ...

polterguy commented 8 years ago

https://phosphorusfive.wordpress.com/2016/01/21/howto-create-a-pgp-keypair-and-import-into-gnupg-using-mimekit/

polterguy commented 8 years ago

Thx, you're a champ, as always :)

jstedfast commented 8 years ago

I've just added Import() methods that take Pgp[Public,Secret]KeyRing[Bundle]s

polterguy commented 8 years ago

Thx :) I think others will appreciate it ...

polterguy commented 8 years ago

BTW, for the record ... I don't understand why, but my key is listed as "invalid" in GPGTools. When I look at the key using "Details", it does not seem to associate the public key as a "sub key" of my private key. I can still encrypt and decrypt using the key, but GPGTools doesn't "like it" for some reasons ...?

My guess is that the keys are not associated with each others correctly ...?

jstedfast commented 8 years ago

Keyrings are how gpg does subkeys, so what I think you need to do is create a PgpSecretKeyRing and add the secret key and then add the public key.

Anyway, I've published MimeKit 1.2.20 that adds the new Import() methods.

polterguy commented 8 years ago

Thx Jeffrey, I'll do an update :)

PS! I finally figured out how to create a key ring, with a "master key" and a "sub key" for daily use, etc, and have them correctly associated with each other. Here's a little article about how I use your baby, to create a key pair ...

https://phosphorusfive.wordpress.com/2016/01/26/how-i-create-new-pgp-key-pairs-in-phosphorus-five/