From a03932725f1c6191c421d20e70a4c9cb976c4e7a Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Sat, 2 Sep 2017 13:12:20 -0400 Subject: [PATCH] Added OpenPgpContext.SignKey() and unit tests Completes issue #315 --- MimeKit/Cryptography/OpenPgpContext.cs | 66 ++++++++++++++----- .../Cryptography/OpenPgpKeyCertification.cs | 65 ++++++++++++++++++ MimeKit/MimeKit.Android.csproj | 1 + MimeKit/MimeKit.Mac.csproj | 1 + MimeKit/MimeKit.Net35.csproj | 1 + MimeKit/MimeKit.Net40.csproj | 1 + MimeKit/MimeKit.Net45.csproj | 1 + MimeKit/MimeKit.NetStandard.csproj | 1 + MimeKit/MimeKit.Portable.csproj | 1 + MimeKit/MimeKit.TvOS.csproj | 1 + MimeKit/MimeKit.WatchOS.csproj | 1 + MimeKit/MimeKit.WindowsUniversal81.csproj | 1 + MimeKit/MimeKit.iOS.csproj | 1 + UnitTests/PgpMimeTests.cs | 43 ++++++++++++ 14 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 MimeKit/Cryptography/OpenPgpKeyCertification.cs diff --git a/MimeKit/Cryptography/OpenPgpContext.cs b/MimeKit/Cryptography/OpenPgpContext.cs index ff214f82da..d66835dce5 100644 --- a/MimeKit/Cryptography/OpenPgpContext.cs +++ b/MimeKit/Cryptography/OpenPgpContext.cs @@ -1114,30 +1114,62 @@ public void GenerateKeyPair (MailboxAddress mailbox, string password, DateTime? Import (generator.GeneratePublicKeyRing ()); } - //public void SignPublicKey (PgpSecretKey secretKey, PgpPublicKey publicKey, DigestAlgorithm digestAlgo = DigestAlgorithm.Sha1) - //{ - // if (secretKey == null) - // throw new ArgumentNullException (nameof (secretKey)); + /// + /// Sign a public key. + /// + /// + /// Signs a public key using the specified secret key. + /// Most OpenPGP implementations use + /// to make their "key signatures". Some implementations are known to use the other + /// certification types, but few differentiate between them. + /// + /// The secret key to use for signing. + /// The public key to sign. + /// The digest algorithm. + /// The certification to give the signed key. + /// + /// is null. + /// -or- + /// is null. + /// + public void SignKey (PgpSecretKey secretKey, PgpPublicKey publicKey, DigestAlgorithm digestAlgo = DigestAlgorithm.Sha1, OpenPgpKeyCertification certification = OpenPgpKeyCertification.GenericCertification) + { + if (secretKey == null) + throw new ArgumentNullException (nameof (secretKey)); - // if (publicKey == null) - // throw new ArgumentNullException (nameof (publicKey)); + if (publicKey == null) + throw new ArgumentNullException (nameof (publicKey)); - // var privateKey = GetPrivateKey (secretKey); - // var signatureGenerator = new PgpSignatureGenerator (secretKey.PublicKey.Algorithm, GetHashAlgorithm (digestAlgo)); + var privateKey = GetPrivateKey (secretKey); + var signatureGenerator = new PgpSignatureGenerator (secretKey.PublicKey.Algorithm, GetHashAlgorithm (digestAlgo)); - // signatureGenerator.InitSign (PgpSignature.CasualCertification, privateKey); - // signatureGenerator.GenerateOnePassVersion (false); + signatureGenerator.InitSign ((int) certification, privateKey); + signatureGenerator.GenerateOnePassVersion (false); - // var subpacketGenerator = new PgpSignatureSubpacketGenerator (); - // var subpacketVector = subpacketGenerator.Generate (); + var subpacketGenerator = new PgpSignatureSubpacketGenerator (); + var subpacketVector = subpacketGenerator.Generate (); - // signatureGenerator.SetHashedSubpackets (subpacketVector); + signatureGenerator.SetHashedSubpackets (subpacketVector); - // var signedKey = PgpPublicKey.AddCertification (publicKey, signatureGenerator.Generate ()); - // var keyring = new PgpPublicKeyRing (signedKey.GetEncoded ()); + var signedKey = PgpPublicKey.AddCertification (publicKey, signatureGenerator.Generate ()); + PgpPublicKeyRing keyring = null; - // Import (keyring); - //} + foreach (var ring in EnumeratePublicKeyRings ()) { + foreach (PgpPublicKey key in ring.GetPublicKeys ()) { + if (key.KeyId == publicKey.KeyId) { + PublicKeyRingBundle = PgpPublicKeyRingBundle.RemovePublicKeyRing (PublicKeyRingBundle, ring); + keyring = PgpPublicKeyRing.InsertPublicKey (ring, signedKey); + break; + } + } + } + + if (keyring == null) + keyring = new PgpPublicKeyRing (signedKey.GetEncoded ()); + + PublicKeyRingBundle = PgpPublicKeyRingBundle.AddPublicKeyRing (PublicKeyRingBundle, keyring); + SavePublicKeyRingBundle (); + } /// /// Gets the equivalent for the diff --git a/MimeKit/Cryptography/OpenPgpKeyCertification.cs b/MimeKit/Cryptography/OpenPgpKeyCertification.cs new file mode 100644 index 0000000000..01b88104ae --- /dev/null +++ b/MimeKit/Cryptography/OpenPgpKeyCertification.cs @@ -0,0 +1,65 @@ +// +// OpenPgpKeyCertification.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +namespace MimeKit.Cryptography +{ + /// + /// An OpenPGP key certification. + /// + /// + /// An OpenPGP key certification. + /// + public enum OpenPgpKeyCertification { + /// + /// Generic certification of a User ID and Public-Key packet. + /// The issuer of this certification does not make any particular + /// assertion as to how well the certifier has checked that the owner + /// of the key is in fact the person described by the User ID. + /// + GenericCertification = 0x10, + + /// + /// Persona certification of a User ID and Public-Key packet. + /// The issuer of this certification has not done any verification of + /// the claim that the owner of this key is the User ID specified. + /// + PersonaCertification = 0x11, + + /// + /// Casual certification of a User ID and Public-Key packet. + /// The issuer of this certification has done some casual + /// verification of the claim of identity. + /// + CasualCertification = 0x12, + + /// + /// Positive certification of a User ID and Public-Key packet. + /// The issuer of this certification has done substantial + /// verification of the claim of identity. + /// + PositiveCertification = 0x13 + } +} diff --git a/MimeKit/MimeKit.Android.csproj b/MimeKit/MimeKit.Android.csproj index 210b209fbe..d990c94ef6 100644 --- a/MimeKit/MimeKit.Android.csproj +++ b/MimeKit/MimeKit.Android.csproj @@ -96,6 +96,7 @@ + diff --git a/MimeKit/MimeKit.Mac.csproj b/MimeKit/MimeKit.Mac.csproj index f3fa222678..a7937566cc 100644 --- a/MimeKit/MimeKit.Mac.csproj +++ b/MimeKit/MimeKit.Mac.csproj @@ -86,6 +86,7 @@ + diff --git a/MimeKit/MimeKit.Net35.csproj b/MimeKit/MimeKit.Net35.csproj index e0c6a8fd9e..46615fd1ca 100644 --- a/MimeKit/MimeKit.Net35.csproj +++ b/MimeKit/MimeKit.Net35.csproj @@ -91,6 +91,7 @@ + diff --git a/MimeKit/MimeKit.Net40.csproj b/MimeKit/MimeKit.Net40.csproj index 2e07266efc..61b6cdd317 100644 --- a/MimeKit/MimeKit.Net40.csproj +++ b/MimeKit/MimeKit.Net40.csproj @@ -93,6 +93,7 @@ + diff --git a/MimeKit/MimeKit.Net45.csproj b/MimeKit/MimeKit.Net45.csproj index 40914c9626..0a35b5ab3e 100644 --- a/MimeKit/MimeKit.Net45.csproj +++ b/MimeKit/MimeKit.Net45.csproj @@ -94,6 +94,7 @@ + diff --git a/MimeKit/MimeKit.NetStandard.csproj b/MimeKit/MimeKit.NetStandard.csproj index 1182a62232..73d5a53ac7 100644 --- a/MimeKit/MimeKit.NetStandard.csproj +++ b/MimeKit/MimeKit.NetStandard.csproj @@ -81,6 +81,7 @@ + diff --git a/MimeKit/MimeKit.Portable.csproj b/MimeKit/MimeKit.Portable.csproj index edcd395342..fd560f8992 100644 --- a/MimeKit/MimeKit.Portable.csproj +++ b/MimeKit/MimeKit.Portable.csproj @@ -84,6 +84,7 @@ + diff --git a/MimeKit/MimeKit.TvOS.csproj b/MimeKit/MimeKit.TvOS.csproj index 80bbfe25ad..a045b58823 100644 --- a/MimeKit/MimeKit.TvOS.csproj +++ b/MimeKit/MimeKit.TvOS.csproj @@ -78,6 +78,7 @@ + diff --git a/MimeKit/MimeKit.WatchOS.csproj b/MimeKit/MimeKit.WatchOS.csproj index f75bea27f0..bf38c8c8c4 100644 --- a/MimeKit/MimeKit.WatchOS.csproj +++ b/MimeKit/MimeKit.WatchOS.csproj @@ -84,6 +84,7 @@ + diff --git a/MimeKit/MimeKit.WindowsUniversal81.csproj b/MimeKit/MimeKit.WindowsUniversal81.csproj index 3eadc50ac3..966e68eaea 100644 --- a/MimeKit/MimeKit.WindowsUniversal81.csproj +++ b/MimeKit/MimeKit.WindowsUniversal81.csproj @@ -90,6 +90,7 @@ + diff --git a/MimeKit/MimeKit.iOS.csproj b/MimeKit/MimeKit.iOS.csproj index 06fee186d7..836a288ed5 100644 --- a/MimeKit/MimeKit.iOS.csproj +++ b/MimeKit/MimeKit.iOS.csproj @@ -93,6 +93,7 @@ + diff --git a/UnitTests/PgpMimeTests.cs b/UnitTests/PgpMimeTests.cs index d5d70c108a..ba471a02cc 100644 --- a/UnitTests/PgpMimeTests.cs +++ b/UnitTests/PgpMimeTests.cs @@ -31,6 +31,7 @@ using NUnit.Framework; +using Org.BouncyCastle.Bcpg; using Org.BouncyCastle.Bcpg.OpenPgp; using MimeKit; @@ -136,6 +137,48 @@ public void TestKeyGeneration () } } + [Test] + public void TestKeySigning () + { + using (var ctx = new DummyOpenPgpContext ()) { + var seckey = ctx.EnumerateSecretKeys (new MailboxAddress ("", "mimekit@example.com")).FirstOrDefault (); + var mailbox = new MailboxAddress ("Snarky McSnarkypants", "snarky@snarkypants.net"); + + ctx.GenerateKeyPair (mailbox, "password", DateTime.Now.AddYears (1)); + + // delete the secret keyring, we don't need it + var secring = ctx.EnumerateSecretKeyRings (mailbox).FirstOrDefault (); + ctx.Delete (secring); + + var pubring = ctx.EnumeratePublicKeyRings (mailbox).FirstOrDefault (); + var pubkey = pubring.GetPublicKey (); + int sigCount = 0; + + foreach (PgpSignature sig in pubkey.GetKeySignatures ()) + sigCount++; + + Assert.AreEqual (0, sigCount); + + ctx.SignKey (seckey, pubkey, DigestAlgorithm.Sha256, OpenPgpKeyCertification.CasualCertification); + + pubring = ctx.EnumeratePublicKeyRings (mailbox).FirstOrDefault (); + pubkey = pubring.GetPublicKey (); + + sigCount = 0; + + foreach (PgpSignature sig in pubkey.GetKeySignatures ()) { + Assert.AreEqual (seckey.KeyId, sig.KeyId); + Assert.AreEqual (HashAlgorithmTag.Sha256, sig.HashAlgorithm); + Assert.AreEqual ((int) OpenPgpKeyCertification.CasualCertification, sig.SignatureType); + sigCount++; + } + + Assert.AreEqual (1, sigCount); + + ctx.Delete (pubring); + } + } + [Test] public void TestMimeMessageSign () {