From b5f50578a41d9197230d44e000d441e653ee56bc Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Fri, 4 Dec 2020 11:21:22 +0100 Subject: [PATCH 1/8] Initial implementation of OpenPGP EdDSA and usage of the Curve25519 in ECDH. --- crypto/src/asn1/gnu/GNUObjectIdentifiers.cs | 6 +-- crypto/src/asn1/misc/MiscObjectIdentifiers.cs | 3 ++ crypto/src/bcpg/PublicKeyPacket.cs | 1 + crypto/src/bcpg/SignaturePacket.cs | 1 + crypto/src/crypto/ec/CustomNamedCurves.cs | 5 +- .../src/crypto/signers/EdDsa255519Signer.cs | 47 +++++++++++++++++++ .../src/openpgp/PgpEncryptedDataGenerator.cs | 46 +++++++++++++----- crypto/src/openpgp/PgpPublicKey.cs | 13 ++++- .../src/openpgp/PgpPublicKeyEncryptedData.cs | 25 ++++++++-- crypto/src/openpgp/PgpSecretKey.cs | 10 +++- crypto/src/openpgp/PgpUtilities.cs | 5 +- crypto/src/openpgp/Rfc6637Utilities.cs | 18 +++++-- crypto/src/security/SignerUtilities.cs | 7 +++ 13 files changed, 156 insertions(+), 31 deletions(-) create mode 100644 crypto/src/crypto/signers/EdDsa255519Signer.cs diff --git a/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs b/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs index b322ef233b..6688ac1786 100644 --- a/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs +++ b/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs @@ -28,9 +28,9 @@ public abstract class GnuObjectIdentifiers public static readonly DerObjectIdentifier Crc = new DerObjectIdentifier("1.3.6.1.4.1.11591.14"); // CRC algorithms public static readonly DerObjectIdentifier Crc32 = new DerObjectIdentifier("1.3.6.1.4.1.11591.14.1"); // CRC 32 - /** 1.3.6.1.4.1.11591.15 - ellipticCurve */ - public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier("1.3.6.1.4.1.11591.15"); + /** 1.3.6.1.4.1.11591.15 - ellipticCurve */ + public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier("1.3.6.1.4.1.11591.15"); - public static readonly DerObjectIdentifier Ed25519 = EllipticCurve.Branch("1"); + public static readonly DerObjectIdentifier Ed25519 = EllipticCurve.Branch("1"); } } diff --git a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs index 1f101882f3..0ca5f289b6 100644 --- a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs +++ b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs @@ -74,6 +74,9 @@ public abstract class MiscObjectIdentifiers public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_CFB = cryptlib_algorithm.Branch("1.3"); public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_OFB = cryptlib_algorithm.Branch("1.4"); + // OpenPGP (draft-ietf-openpgp-rfc4880bis-10) + public static readonly DerObjectIdentifier Curve25519 = cryptlib_algorithm.Branch("5.1"); + // // Blake2b // diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs index bbed941dc7..f3b07ecdd0 100644 --- a/crypto/src/bcpg/PublicKeyPacket.cs +++ b/crypto/src/bcpg/PublicKeyPacket.cs @@ -48,6 +48,7 @@ internal PublicKeyPacket( key = new ECDHPublicBcpgKey(bcpgIn); break; case PublicKeyAlgorithmTag.ECDsa: + case PublicKeyAlgorithmTag.EdDsa: key = new ECDsaPublicBcpgKey(bcpgIn); break; default: diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs index 70138d584f..fb236dc533 100644 --- a/crypto/src/bcpg/SignaturePacket.cs +++ b/crypto/src/bcpg/SignaturePacket.cs @@ -147,6 +147,7 @@ internal SignaturePacket( signature = new MPInteger[]{ p, g, y }; break; case PublicKeyAlgorithmTag.ECDsa: + case PublicKeyAlgorithmTag.EdDsa: MPInteger ecR = new MPInteger(bcpgIn); MPInteger ecS = new MPInteger(bcpgIn); signature = new MPInteger[]{ ecR, ecS }; diff --git a/crypto/src/crypto/ec/CustomNamedCurves.cs b/crypto/src/crypto/ec/CustomNamedCurves.cs index 0eee66a338..53d0cf979e 100644 --- a/crypto/src/crypto/ec/CustomNamedCurves.cs +++ b/crypto/src/crypto/ec/CustomNamedCurves.cs @@ -3,6 +3,8 @@ using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.GM; +using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Math; @@ -789,7 +791,8 @@ private static void DefineCurveAlias(string name, DerObjectIdentifier oid) static CustomNamedCurves() { - DefineCurve("curve25519", Curve25519Holder.Instance); + DefineCurveWithOid("ed25519", GnuObjectIdentifiers.Ed25519, Curve25519Holder.Instance); + DefineCurveWithOid("curve25519", MiscObjectIdentifiers.Curve25519, Curve25519Holder.Instance); //DefineCurveWithOid("secp112r1", SecObjectIdentifiers.SecP112r1, SecP112R1Holder.Instance); //DefineCurveWithOid("secp112r2", SecObjectIdentifiers.SecP112r2, SecP112R2Holder.Instance); diff --git a/crypto/src/crypto/signers/EdDsa255519Signer.cs b/crypto/src/crypto/signers/EdDsa255519Signer.cs new file mode 100644 index 0000000000..345f5ddb23 --- /dev/null +++ b/crypto/src/crypto/signers/EdDsa255519Signer.cs @@ -0,0 +1,47 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class EdDsa22519Signer : IDsa + { + private Ed25519Signer signer; + + public virtual string AlgorithmName => "EDDSA"; + + public EdDsa22519Signer() + { + signer = new Ed25519Signer(); + } + + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + signer.Init(forSigning, parameters); + } + + public virtual BigInteger[] GenerateSignature(byte[] message) + { + signer.BlockUpdate(message, 0, message.Length); + byte[] sigBytes = signer.GenerateSignature(); + byte[] rBytes = new byte[32]; + Array.Copy(sigBytes, rBytes, 32); + byte[] sBytes = new byte[sigBytes.Length - 32]; + Array.Copy(sigBytes, 32, sBytes, 0, sigBytes.Length - 32); + BigInteger r = new BigInteger(1, rBytes); + BigInteger s = new BigInteger(1, sBytes); + return new BigInteger[2] { r, s }; + } + + public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s) + { + signer.BlockUpdate(message, 0, message.Length); + byte[] rBytes = r.ToByteArrayUnsigned(); + byte[] sBytes = s.ToByteArrayUnsigned(); + byte[] sigBytes = new byte[rBytes.Length + sBytes.Length]; + Array.Copy(rBytes, sigBytes, rBytes.Length); + Array.Copy(sBytes, 0, sigBytes, rBytes.Length, sBytes.Length); + return signer.VerifySignature(sigBytes); + } + } +} diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs index 336baf00d7..81847cabd3 100644 --- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs +++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; +using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.IO; @@ -10,6 +11,7 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Rfc7748; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; @@ -105,7 +107,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH) { IBufferedCipher c; - switch (pubKey.Algorithm) + switch (pubKey.Algorithm) { case PublicKeyAlgorithmTag.RsaEncrypt: case PublicKeyAlgorithmTag.RsaGeneral: @@ -119,29 +121,49 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) throw new PgpException("Can't use DSA for encryption."); case PublicKeyAlgorithmTag.ECDsa: throw new PgpException("Can't use ECDSA for encryption."); + case PublicKeyAlgorithmTag.EdDsa: + throw new PgpException("Can't use EdDSA for encryption."); default: throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm); } AsymmetricKeyParameter akp = pubKey.GetKey(); - c.Init(true, new ParametersWithRandom(akp, random)); + c.Init(true, new ParametersWithRandom(akp, random)); return c.DoFinal(sessionInfo); } ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key; + KeyParameter key; + byte[] encodedPublicKey; - // Generate the ephemeral key pair - IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH"); - gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random)); + if (ecKey.CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) + { + byte[] privateKey = new byte[X25519.PointSize]; + X25519.GeneratePrivateKey(random, privateKey); + byte[] sharedKey = new byte[32]; + X25519.CalculateAgreement(privateKey, 0, BigIntegers.AsUnsignedByteArray(ecKey.EncodedPoint), 1, sharedKey, 0); + byte[] publicKey = new byte[X25519.PointSize + 1]; + publicKey[0] = 0x40; // compressed point + X25519.GeneratePublicKey(privateKey, 0, publicKey, 1); + encodedPublicKey = publicKey; + key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, sharedKey)); + } + else + { + // Generate the ephemeral key pair + IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH"); + gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random)); - AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); - ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private; - ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public; + AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); + ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private; + ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public; - ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey(); - ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize(); + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey(); + ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize(); - KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S)); + key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S)); + encodedPublicKey = ephPub.Q.GetEncoded(false); + } IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm); w.Init(true, new ParametersWithRandom(key, random)); @@ -149,7 +171,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo, sessionKeyObfuscation); byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length); - byte[] VB = new MPInteger(new BigInteger(1, ephPub.Q.GetEncoded(false))).GetEncoded(); + byte[] VB = new MPInteger(new BigInteger(1, encodedPublicKey)).GetEncoded(); byte[] rv = new byte[VB.Length + 1 + C.Length]; diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index 92422c4132..b66ca844f2 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.IO; - +using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; @@ -177,6 +177,10 @@ public PgpPublicKey( { bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q); } + else if (algorithm == PublicKeyAlgorithmTag.EdDsa) + { + bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q); + } else { throw new PgpException("unknown EC algorithm"); @@ -495,7 +499,12 @@ public AsymmetricKeyParameter GetKey() case PublicKeyAlgorithmTag.ECDsa: return GetECKey("ECDSA"); case PublicKeyAlgorithmTag.ECDH: - return GetECKey("ECDH"); + if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) + return new X25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 0); + else + return GetECKey("ECDH"); + case PublicKeyAlgorithmTag.EdDsa: + return new Ed25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 1); case PublicKeyAlgorithmTag.ElGamalEncrypt: case PublicKeyAlgorithmTag.ElGamalGeneral: ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key; diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs index 04fe3ad37c..be05f36fe4 100644 --- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs +++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs @@ -8,8 +8,11 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Custom.Djb; +using Org.BouncyCastle.Math.EC.Rfc7748; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { @@ -210,12 +213,24 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey) byte[] keyEnc = new byte[keyLen]; Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length); - ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc); + KeyParameter key; - ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key; - ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize(); - - KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S)); + if (privKey.Key is X25519PrivateKeyParameters x25519privKeyParams) + { + byte[] sharedKey = new byte[32]; + byte[] reversedPrivateKey = new byte[32]; + x25519privKeyParams.Encode(reversedPrivateKey, 0); + Array.Reverse((Array)reversedPrivateKey); + X25519.ScalarMult(reversedPrivateKey, 0, pEnc, 1, sharedKey, 0); + key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, sharedKey)); + } + else + { + ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc); + ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key; + ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize(); + key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S)); + } IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm); w.Init(false, key); diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index a3ffd4a4ad..7271fa2ef4 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.IO; - +using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; @@ -674,11 +674,17 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams); break; case PublicKeyAlgorithmTag.ECDH: - privateKey = GetECKey("ECDH", bcpgIn); + if (((ECPublicBcpgKey)secret.PublicKeyPacket.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) + privateKey = new X25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0); + else + privateKey = GetECKey("ECDH", bcpgIn); break; case PublicKeyAlgorithmTag.ECDsa: privateKey = GetECKey("ECDSA", bcpgIn); break; + case PublicKeyAlgorithmTag.EdDsa: + privateKey = new Ed25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0); + break; case PublicKeyAlgorithmTag.ElGamalEncrypt: case PublicKeyAlgorithmTag.ElGamalGeneral: ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key; diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs index fd5ab62328..cc6b211d5d 100644 --- a/crypto/src/openpgp/PgpUtilities.cs +++ b/crypto/src/openpgp/PgpUtilities.cs @@ -95,7 +95,10 @@ public static string GetSignatureName( case PublicKeyAlgorithmTag.ECDsa: encAlg = "ECDSA"; break; - case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases. + case PublicKeyAlgorithmTag.EdDsa: + encAlg = "EdDSA"; + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases. case PublicKeyAlgorithmTag.ElGamalGeneral: encAlg = "ElGamal"; break; diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs index 5d992ec515..97a2baaf14 100644 --- a/crypto/src/openpgp/Rfc6637Utilities.cs +++ b/crypto/src/openpgp/Rfc6637Utilities.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Org.BouncyCastle.Asn1; @@ -70,12 +70,17 @@ public static int GetKeyLength(SymmetricKeyAlgorithmTag algID) public static byte[] CreateKey(PublicKeyPacket pubKeyData, ECPoint s) { byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData); - ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key; - return Kdf(ecKey.HashAlgorithm, s, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial); } + public static byte[] CreateKey(PublicKeyPacket pubKeyData, byte[] ZB) + { + byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData); + ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key; + return Kdf(ecKey.HashAlgorithm, ZB, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial); + } + // RFC 6637 - Section 8 // curve_OID_len = (byte)len(curve_OID); // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 @@ -118,10 +123,13 @@ public static byte[] CreateUserKeyingMaterial(PublicKeyPacket pubKeyData) // return oBits leftmost bits of MB. private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters) { - byte[] ZB = s.XCoord.GetEncoded(); + return Kdf(digestAlg, s.XCoord.GetEncoded(), keyLen, parameters); + } + private static byte[] Kdf(HashAlgorithmTag digestAlg, byte[] ZB, int keyLen, byte[] parameters) + { string digestName = PgpUtilities.GetDigestName(digestAlg); - IDigest digest = DigestUtilities.GetDigest(digestName); + IDigest digest = DigestUtilities.GetDigest(digestName); digest.Update(0x00); digest.Update(0x00); diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs index 8a289897e9..1f263d17fa 100644 --- a/crypto/src/security/SignerUtilities.cs +++ b/crypto/src/security/SignerUtilities.cs @@ -610,6 +610,13 @@ public static ISigner GetSigner( return new DsaDigestSigner(new ECDsaSigner(), digest); } + if (Platform.EndsWith(mechanism, "WITHEDDSA")) + { + string digestName = mechanism.Substring(0, mechanism.LastIndexOf("WITH")); + IDigest digest = DigestUtilities.GetDigest(digestName); + return new DsaDigestSigner(new EdDsa22519Signer(), digest); + } + if (Platform.EndsWith(mechanism, "withCVC-ECDSA") || Platform.EndsWith(mechanism, "withPLAIN-ECDSA")) { From a52b34b83ea6fcc4c3931ba1ca0c3080afae6069 Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Fri, 11 Dec 2020 17:31:32 +0100 Subject: [PATCH 2/8] Add unit test for EdDSA, update S Expression parser to support more of the syntax used by GnuPG --- crypto/src/openpgp/PgpPublicKey.cs | 10 + crypto/src/openpgp/PgpSecretKey.cs | 145 ++++++++---- crypto/src/openpgp/SXprUtilities.cs | 212 ++++++++++++++--- crypto/test/src/openpgp/test/PgpEdDsaTest.cs | 232 +++++++++++++++++++ 4 files changed, 522 insertions(+), 77 deletions(-) create mode 100644 crypto/test/src/openpgp/test/PgpEdDsaTest.cs diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index b66ca844f2..0a5137d3ad 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.IO; +using Org.BouncyCastle.Asn1.Gnu; using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; @@ -10,6 +11,7 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Rfc8032; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Collections; @@ -186,6 +188,14 @@ public PgpPublicKey( throw new PgpException("unknown EC algorithm"); } } + else if (pubKey is Ed25519PublicKeyParameters) + { + Ed25519PublicKeyParameters ecK = (Ed25519PublicKeyParameters)pubKey; + byte[] encodedPoint = new byte[Ed25519.PublicKeySize + 1]; + encodedPoint[0] = 0x40; + ecK.Encode(encodedPoint, 1); + bcpgKey = new ECDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, encodedPoint)); + } else if (pubKey is ElGamalPublicKeyParameters) { ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey; diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 7271fa2ef4..60774aa051 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -1,9 +1,12 @@ using System; using System.Collections; +using System.Diagnostics; using System.IO; +using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.EC; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; @@ -57,6 +60,10 @@ internal PgpSecretKey( ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key; secKey = new ECSecretBcpgKey(ecK.D); break; + case PublicKeyAlgorithmTag.EdDsa: + Ed25519PrivateKeyParameters edK = (Ed25519PrivateKeyParameters)privKey.Key; + secKey = new ECSecretBcpgKey(new BigInteger(1, edK.GetEncoded())); + break; case PublicKeyAlgorithmTag.ElGamalEncrypt: case PublicKeyAlgorithmTag.ElGamalGeneral: ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key; @@ -1111,24 +1118,26 @@ public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) { - SXprUtilities.SkipOpenParenthesis(inputStream); + SXprUtilities reader = new SXprUtilities(inputStream); + + reader.SkipOpenParenthesis(); - string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string type = reader.ReadString(); if (type.Equals("protected-private-key")) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); string curveName; - string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string keyType = reader.ReadString(); if (keyType.Equals("ecc")) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string curveID = reader.ReadString(); + curveName = reader.ReadString(); - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipCloseParenthesis(); } else { @@ -1137,21 +1146,21 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ byte[] qVal; - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + type = reader.ReadString(); if (type.Equals("q")) { - qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + qVal = reader.ReadBytes(); } else { throw new PgpException("no q value found"); } - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipCloseParenthesis(); - byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); + byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName); // TODO: check SHA-1 hash. return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, @@ -1170,7 +1179,7 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ /// </remarks> public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase) { - return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true); + return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, false), true); } /// <summary> @@ -1181,7 +1190,7 @@ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] pa /// </remarks> public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase) { - return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true); + return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, true), true); } /// <summary> @@ -1192,37 +1201,48 @@ public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[ /// </remarks> public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase) { - return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false); + return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), rawPassPhrase, false); } /// <summary> /// Parse a secret key from one of the GPG S expression keys. /// </summary> - internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase) + internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string type = reader.ReadString(); if (type.Equals("protected-private-key")) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); string curveName; + DerObjectIdentifier curveOid; - string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string keyType = reader.ReadString(); if (keyType.Equals("ecc")) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string curveID = reader.ReadString(); + curveName = reader.ReadString(); if (Platform.StartsWith(curveName, "NIST ")) { curveName = curveName.Substring("NIST ".Length); } - SXprUtilities.SkipCloseParenthesis(inputStream); + curveOid = ECNamedCurveTable.GetOid(curveName); + if (curveOid == null) + { + curveOid = CustomNamedCurves.GetOid(curveName); + } + if (curveOid == null) + { + throw new PgpException("unknown curve"); + } + + reader.SkipCloseParenthesis(); } else { @@ -1230,25 +1250,35 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ } byte[] qVal; + string flags = null; - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + type = reader.ReadString(); + if (type == "flags") + { + // Skip over flags + flags = reader.ReadString(); + reader.SkipCloseParenthesis(); + reader.SkipOpenParenthesis(); + type = reader.ReadString(); + } if (type.Equals("q")) { - qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + qVal = reader.ReadBytes(); } else { throw new PgpException("no q value found"); } - PublicKeyPacket pubPacket = new PublicKeyPacket(PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, - new ECDsaPublicBcpgKey(ECNamedCurveTable.GetOid(curveName), new BigInteger(1, qVal))); + PublicKeyPacket pubPacket = new PublicKeyPacket( + flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, + new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal))); - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipCloseParenthesis(); - byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); + byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName); // TODO: check SHA-1 hash. return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null, @@ -1258,51 +1288,68 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ throw new PgpException("unknown key type found"); } - private static byte[] GetDValue(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) + private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) { string type; - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); string protection; S2k s2k; byte[] iv; byte[] secKeyData; - type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + type = reader.ReadString(); if (type.Equals("protected")) { - protection = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + protection = reader.ReadString(); - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - s2k = SXprUtilities.ParseS2k(inputStream); + s2k = reader.ParseS2k(); - iv = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + iv = reader.ReadBytes(); - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipCloseParenthesis(); - secKeyData = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + secKeyData = reader.ReadBytes(); } else { throw new PgpException("protected block not found"); } - // TODO: recognise other algorithms - KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); + // Valid values of protection: openpgp-s2k3-sha1-aes-cbc, openpgp-s2k3-ocb-aes, openpgp-native + byte[] data; + KeyParameter key; + + switch (protection) + { + case "openpgp-s2k3-sha1-aes-cbc": + key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); + data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + break; + + case "openpgp-s2k3-ocb-aes": + key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); + data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/OCB/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + break; - byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + case "openpgp-native": + default: + throw new PgpException(protection + " key format is not supported yet"); + } // // parse the secret key S-expr // Stream keyIn = new MemoryStream(data, false); - SXprUtilities.SkipOpenParenthesis(keyIn); - SXprUtilities.SkipOpenParenthesis(keyIn); - SXprUtilities.SkipOpenParenthesis(keyIn); - String name = SXprUtilities.ReadString(keyIn, keyIn.ReadByte()); - return SXprUtilities.ReadBytes(keyIn, keyIn.ReadByte()); + reader = new SXprUtilities(keyIn); + reader.SkipOpenParenthesis(); + reader.SkipOpenParenthesis(); + reader.SkipOpenParenthesis(); + String name = reader.ReadString(); + return reader.ReadBytes(); } } } diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprUtilities.cs index 68ff373a83..7cd4862065 100644 --- a/crypto/src/openpgp/SXprUtilities.cs +++ b/crypto/src/openpgp/SXprUtilities.cs @@ -1,6 +1,7 @@ using System; using System.IO; - +using System.Text; +using Org.BouncyCastle.Utilities.Encoders; using Org.BouncyCastle.Utilities.IO; namespace Org.BouncyCastle.Bcpg.OpenPgp @@ -10,75 +11,230 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp * <p> * Format documented here: * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master + * http://people.csail.mit.edu/rivest/Sexp.txt * </p> */ - public sealed class SXprUtilities + class SXprUtilities { - private SXprUtilities() + Stream stream; + int peekedByte; + + internal SXprUtilities(Stream stream) + { + this.stream = stream; + this.peekedByte = -1; + } + + private int ReadByte() + { + if (this.peekedByte > 0) + { + int pb = this.peekedByte; + this.peekedByte = 0; + return pb; + } + return stream.ReadByte(); + } + + private void UnreadByte(int pb) { + this.peekedByte = pb; } - private static int ReadLength(Stream input, int ch) + private int ReadLength() { - int len = ch - '0'; + int ch; + int len = 0; - while ((ch = input.ReadByte()) >= 0 && ch != ':') + while ((ch = ReadByte()) >= 0 && ch >= '0' && ch <= '9') { - len = len * 10 + ch - '0'; + len = len * 10 + (ch - '0'); } + UnreadByte(ch); return len; } - internal static string ReadString(Stream input, int ch) + public string ReadString() { - int len = ReadLength(input, ch); + SkipWhitespace(); + + int ch = ReadByte(); + if (ch >= '0' && ch <= '9') + { + UnreadByte(ch); + + int len = ReadLength(); + ch = ReadByte(); + if (ch == ':') + { + char[] chars = new char[len]; - char[] chars = new char[len]; + for (int i = 0; i != chars.Length; i++) + { + chars[i] = (char)ReadByte(); + } - for (int i = 0; i != chars.Length; i++) + return new string(chars); + } + else if (ch == '"') + { + return ReadQuotedString(len); + } + throw new IOException("unsupported encoding"); + } + else if (ch == '"') + { + return ReadQuotedString(0); + } + else if (ch == '{' || ch == '|' || ch == '#') { - chars[i] = (char)input.ReadByte(); + // TODO: Unsupported encoding + throw new IOException("unsupported encoding"); } + else + { + StringBuilder sb = new StringBuilder(); + while (IsTokenChar(ch)) + { + sb.Append((char)ch); + ch = (char)ReadByte(); + } + UnreadByte(ch); + return sb.ToString(); + } + } - return new string(chars); + private string ReadQuotedString(int length) + { + StringBuilder sb = new StringBuilder(length); + int ch; + bool skipNewLine = false; + do + { + ch = ReadByte(); + if ((ch == '\n' || ch == '\r') && skipNewLine) + { + skipNewLine = false; + } + else if (ch == '\\') + { + ch = (char)ReadByte(); + switch (ch) + { + case 'b': sb.Append('\b'); break; + case 't': sb.Append('\t'); break; + case 'v': sb.Append('\v'); break; + case 'n': sb.Append('\n'); break; + case 'r': sb.Append('\r'); break; + case 'f': sb.Append('\f'); break; + case '"': sb.Append('"'); break; + case '\'': sb.Append('\''); break; + case '\r': + case '\n': + skipNewLine = true; + break; + default: + // TODO: Octal value, hexadecimal value + throw new IOException("unsupported encoding"); + } + } + else if (ch != '"' && ch >= 0) + { + skipNewLine = false; + sb.Append((char)ch); + } + } + while (ch != '"' && ch > 0); + return sb.ToString(); } - internal static byte[] ReadBytes(Stream input, int ch) + private static bool IsTokenChar(int ch) { - int len = ReadLength(input, ch); + return + (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.' || + ch == '/' || ch == '_' || + ch == ':' || ch == '*' || + ch == '+' || ch == '='; + } + + public byte[] ReadBytes() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch >= '0' && ch <= '9') + { + UnreadByte(ch); + + int len = ReadLength(); + + if (ReadByte() != ':') + throw new IOException("unsupported encoding"); - byte[] data = new byte[len]; + byte[] data = new byte[len]; - Streams.ReadFully(input, data); + Streams.ReadFully(stream, data); - return data; + return data; + } + else if (ch == '#') + { + StringBuilder sb = new StringBuilder(); + do + { + ch = ReadByte(); + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) + sb.Append((char)ch); + } + while (ch != '#' && ch >= 0); + return Hex.Decode(sb.ToString()); + } + + throw new IOException("unsupported encoding"); } - internal static S2k ParseS2k(Stream input) + public S2k ParseS2k() { - SkipOpenParenthesis(input); + SkipOpenParenthesis(); - string alg = ReadString(input, input.ReadByte()); - byte[] iv = ReadBytes(input, input.ReadByte()); - long iterationCount = Int64.Parse(ReadString(input, input.ReadByte())); + string alg = ReadString(); + byte[] iv = ReadBytes(); + long iterationCount = Int64.Parse(ReadString()); - SkipCloseParenthesis(input); + SkipCloseParenthesis(); // we have to return the actual iteration count provided. return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); } - internal static void SkipOpenParenthesis(Stream input) + private void SkipWhitespace() { - int ch = input.ReadByte(); + int ch = ReadByte(); + while (ch == ' ' || ch == '\r' || ch == '\n') + { + ch = ReadByte(); + } + UnreadByte(ch); + } + + public void SkipOpenParenthesis() + { + SkipWhitespace(); + + int ch = ReadByte(); if (ch != '(') throw new IOException("unknown character encountered"); } - internal static void SkipCloseParenthesis(Stream input) + public void SkipCloseParenthesis() { - int ch = input.ReadByte(); + SkipWhitespace(); + + int ch = ReadByte(); if (ch != ')') throw new IOException("unknown character encountered"); } diff --git a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs new file mode 100644 index 0000000000..a670b14902 --- /dev/null +++ b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using NUnit.Framework; +using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests +{ + [TestFixture] + public class PgpEdDsaTest + : SimpleTest + { + private static readonly byte[] testPubKey = + Base64.Decode( + "mDMEX9NCKBYJKwYBBAHaRw8BAQdASPhAQySGGPMjoquv5i1IwLRSDJ2QtmLLvER2" + + "Cm8UZyW0HkVkRFNBIDx0ZXN0LmVkZHNhQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEE" + + "sh83FOYApIfZuLp0emj2ffveCqEFAl/TQigCGwMFCwkIBwIGFQoJCAsCBBYCAwEC" + + "HgECF4AACgkQemj2ffveCqF6XQEA2S08fb0Z6LCd9P+eajPNDm1Wrf/y/7nkNwhb" + + "DvwiU5kBAM16UvHrzX6CvQFvc7aKvPH+4wrvRewvAGK16a4fBHEE"); + + private static readonly byte[] testPrivKey = + Base64.Decode( + "lIYEX9NCKBYJKwYBBAHaRw8BAQdASPhAQySGGPMjoquv5i1IwLRSDJ2QtmLLvER2" + + "Cm8UZyX+BwMC7ubvoFJTTXfOpQ3tDoys52w6tb01rHHtjKVWjXMjiyN8tXHBDC9N" + + "UcMYViTDegBXOEgw4TIKn9mkkTDvP3xVFeMH2XBPzu9e9m8GlBODILQeRWREU0Eg" + + "PHRlc3QuZWRkc2FAZXhhbXBsZS5jb20+iJAEExYIADgWIQSyHzcU5gCkh9m4unR6" + + "aPZ9+94KoQUCX9NCKAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRB6aPZ9" + + "+94KoXpdAQDZLTx9vRnosJ30/55qM80ObVat//L/ueQ3CFsO/CJTmQEAzXpS8evN" + + "foK9AW9ztoq88f7jCu9F7C8AYrXprh8EcQQ="); + + private static readonly char[] testPasswd = "test".ToCharArray(); + + private static readonly byte[] sExprKey = + Base64.Decode( + "KHByb3RlY3RlZC1wcml2YXRlLWtleSAoZWNjIChjdXJ2ZSBFZDI1NTE5KShmbGFn" + + "cyBlZGRzYSkocQogICM0MDQ4Rjg0MDQzMjQ4NjE4RjMyM0EyQUJBRkU2MkQ0OEMw" + + "QjQ1MjBDOUQ5MEI2NjJDQkJDNDQ3NjBBNkYxNDY3MjUjKQogKHByb3RlY3RlZCBv" + + "cGVucGdwLXMyazMtb2NiLWFlcyAoKHNoYTEgI0IwRkY2MDAzRUE4RkQ4QkIjCiAg" + + "Ijc2OTUzNjAiKSM5NDZEREU3QTUxMzAyRUEyRDc3NDNEOTQjKSM4NDBFMTIyRTdB" + + "RDI0RkY1MkE5RUY3QUFDQjgxRUE2CiAyMTkyQjZCMjlCOUI4N0QwNTZBOUE4MTEz" + + "QjIzNjlEREM4QUVGMTJDNjRBN0QwOTEwM0Q1MTU1Nzc0Q0Q5RkQ4NzczQTEzCiBD" + + "NTgwQ0Y4RkY5OEZERTU3RDVGIykocHJvdGVjdGVkLWF0ICIyMDIwMTIxMVQwOTU2" + + "MDEiKSkp"); + + private static readonly byte[] referencePubKey = + Base64.Decode( + "mDMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" + + "Q+47JAY="); + + private static readonly string referenceMessage = "OpenPGP"; + + private static readonly byte[] referenceSignature = + Base64.Decode( + "iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgEAVvkMypjiECY3vZg/2xbBMd/S" + + "ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE"); + + private void ReferenceTest() + { + PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(referencePubKey); + PgpPublicKey publicKey = pubKeyRing.GetPublicKey(); + + PgpObjectFactory pgpFact = new PgpObjectFactory(referenceSignature); + PgpSignatureList signatureList = (PgpSignatureList)pgpFact.NextPgpObject(); + PgpSignature signature = signatureList.Get(0); + signature.InitVerify(publicKey); + signature.Update(Encoding.ASCII.GetBytes(referenceMessage)); + if (!signature.Verify()) + { + Fail("signature failed to verify!"); + } + } + + private void GenerateAndSign() + { + SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); + + IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519"); + keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random)); + + AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair(); + + PgpKeyPair eddsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow); + + byte[] msg = Encoding.ASCII.GetBytes("hello world!"); + + // + // try a signature + // + PgpSignatureGenerator signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256); + signGen.InitSign(PgpSignature.BinaryDocument, eddsaKeyPair.PrivateKey); + + signGen.Update(msg); + + PgpSignature sig = signGen.Generate(); + + sig.InitVerify(eddsaKeyPair.PublicKey); + sig.Update(msg); + + if (!sig.Verify()) + { + Fail("signature failed to verify!"); + } + + // + // generate a key ring + // + char[] passPhrase = "test".ToCharArray(); + PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, eddsaKeyPair, + "test@bouncycastle.org", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random); + + PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing(); + PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing(); + + PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded()); + if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded())) + { + Fail("public key ring encoding failed"); + } + + PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded()); + if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded())) + { + Fail("secret key ring encoding failed"); + } + + + // + // try a signature using encoded key + // + signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256); + signGen.InitSign(PgpSignature.BinaryDocument, secRing.GetSecretKey().ExtractPrivateKey(passPhrase)); + signGen.Update(msg); + + sig = signGen.Generate(); + sig.InitVerify(secRing.GetSecretKey().PublicKey); + sig.Update(msg); + + if (!sig.Verify()) + { + Fail("re-encoded signature failed to verify!"); + } + } + + public override void PerformTest() + { + ReferenceTest(); + + // + // Read the public key + // + PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(testPubKey); + foreach (PgpSignature certification in pubKeyRing.GetPublicKey().GetSignatures()) + { + certification.InitVerify(pubKeyRing.GetPublicKey()); + + if (!certification.VerifyCertification((string)First(pubKeyRing.GetPublicKey().GetUserIds()), pubKeyRing.GetPublicKey())) + { + Fail("self certification does not verify"); + } + } + + /*if (pubKeyRing.GetPublicKey().BitStrength != 256) + { + Fail("incorrect bit strength returned"); + }*/ + + // + // Read the private key + // + PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testPrivKey); + + PgpPrivateKey privKey = secretKeyRing.GetSecretKey().ExtractPrivateKey(testPasswd); + + GenerateAndSign(); + + // + // sExpr + // + // TODO: Fails the OCB MAC check when decoding key but works otherwise + /*byte[] msg = Encoding.ASCII.GetBytes("hello world!"); + + PgpSecretKey key = PgpSecretKey.ParseSecretKeyFromSExpr(new MemoryStream(sExprKey, false), "test".ToCharArray()); + + PgpSignatureGenerator signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256); + signGen.InitSign(PgpSignature.BinaryDocument, key.ExtractPrivateKey(null)); + signGen.Update(msg); + + PgpSignature sig = signGen.Generate(); + sig.InitVerify(key.PublicKey); + sig.Update(msg); + + if (!sig.Verify()) + { + Fail("signature failed to verify!"); + }*/ + } + + private static object First(IEnumerable e) + { + IEnumerator n = e.GetEnumerator(); + Assert.IsTrue(n.MoveNext()); + return n.Current; + } + + public override string Name + { + get { return "PgpEdDsaTest"; } + } + + public static void Main( + string[] args) + { + RunTest(new PgpECDsaTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + } +} From 9f98e0cd6f41169f0cb8e012a7916f2bfebcb6c8 Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Sun, 13 Dec 2020 11:07:59 +0100 Subject: [PATCH 3/8] Rename SXprUtilities to SXprReader Add SXprWriter to help writing canonical S expression Fix decoding AES-OCB encrypted S expression keys --- crypto/src/openpgp/PgpSecretKey.cs | 166 ++++++++++-------- .../{SXprUtilities.cs => SXprReader.cs} | 8 +- crypto/src/openpgp/SXprWriter.cs | 50 ++++++ crypto/test/src/openpgp/test/PgpEdDsaTest.cs | 5 +- 4 files changed, 151 insertions(+), 78 deletions(-) rename crypto/src/openpgp/{SXprUtilities.cs => SXprReader.cs} (97%) create mode 100644 crypto/src/openpgp/SXprWriter.cs diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 60774aa051..4c44fbdf57 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Diagnostics; using System.IO; +using crypto.openpgp; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.X9; @@ -1116,60 +1117,6 @@ public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey); } - internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) - { - SXprUtilities reader = new SXprUtilities(inputStream); - - reader.SkipOpenParenthesis(); - - string type = reader.ReadString(); - if (type.Equals("protected-private-key")) - { - reader.SkipOpenParenthesis(); - - string curveName; - - string keyType = reader.ReadString(); - if (keyType.Equals("ecc")) - { - reader.SkipOpenParenthesis(); - - string curveID = reader.ReadString(); - curveName = reader.ReadString(); - - reader.SkipCloseParenthesis(); - } - else - { - throw new PgpException("no curve details found"); - } - - byte[] qVal; - - reader.SkipOpenParenthesis(); - - type = reader.ReadString(); - if (type.Equals("q")) - { - qVal = reader.ReadBytes(); - } - else - { - throw new PgpException("no q value found"); - } - - reader.SkipCloseParenthesis(); - - byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName); - // TODO: check SHA-1 hash. - - return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, - new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey); - } - - throw new PgpException("unknown key type found"); - } - /// <summary> /// Parse a secret key from one of the GPG S expression keys. /// </summary> @@ -1179,7 +1126,7 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ /// </remarks> public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase) { - return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, false), true); + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, null); } /// <summary> @@ -1190,7 +1137,7 @@ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] pa /// </remarks> public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase) { - return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, true), true); + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, null); } /// <summary> @@ -1201,14 +1148,16 @@ public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[ /// </remarks> public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase) { - return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), rawPassPhrase, false); + return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, null); } /// <summary> /// Parse a secret key from one of the GPG S expression keys. /// </summary> - internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase) + internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) { + SXprReader reader = new SXprReader(inputStream); + reader.SkipOpenParenthesis(); string type = reader.ReadString(); @@ -1272,28 +1221,87 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byt throw new PgpException("no q value found"); } - PublicKeyPacket pubPacket = new PublicKeyPacket( - flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, - new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal))); + if (pubKey == null) + { + PublicKeyPacket pubPacket = new PublicKeyPacket( + flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, + new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal))); + pubKey = new PgpPublicKey(pubPacket); + } reader.SkipCloseParenthesis(); - byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName); - // TODO: check SHA-1 hash. + byte[] dValue = GetDValue(reader, pubKey.PublicKeyPacket, rawPassPhrase, clearPassPhrase, curveName); - return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null, - new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), new PgpPublicKey(pubPacket)); + return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, + new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey); } throw new PgpException("unknown key type found"); } - private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) + private static void WriteSExprPublicKey(SXprWriter writer, PublicKeyPacket pubPacket, string curveName, string protectedAt) + { + writer.StartList(); + switch (pubPacket.Algorithm) + { + case PublicKeyAlgorithmTag.ECDsa: + case PublicKeyAlgorithmTag.EdDsa: + writer.WriteString("ecc"); + writer.StartList(); + writer.WriteString("curve"); + writer.WriteString(curveName); + writer.EndList(); + if (pubPacket.Algorithm == PublicKeyAlgorithmTag.EdDsa) + { + writer.StartList(); + writer.WriteString("flags"); + writer.WriteString("eddsa"); + writer.EndList(); + } + writer.StartList(); + writer.WriteString("q"); + writer.WriteBytes(((ECDsaPublicBcpgKey)pubPacket.Key).EncodedPoint.ToByteArrayUnsigned()); + writer.EndList(); + break; + + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaSign: + case PublicKeyAlgorithmTag.RsaGeneral: + RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)pubPacket.Key; + writer.WriteString("rsa"); + writer.StartList(); + writer.WriteString("n"); + writer.WriteBytes(rsaK.Modulus.ToByteArrayUnsigned()); + writer.EndList(); + writer.StartList(); + writer.WriteString("e"); + writer.WriteBytes(rsaK.PublicExponent.ToByteArrayUnsigned()); + writer.EndList(); + break; + + // TODO: DSA, etc. + default: + throw new PgpException("unsupported algorithm in S expression"); + } + + if (protectedAt != null) + { + writer.StartList(); + writer.WriteString("protected-at"); + writer.WriteString(protectedAt); + writer.EndList(); + } + writer.EndList(); + } + + private static byte[] GetDValue(SXprReader reader, PublicKeyPacket publicKey, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) { string type; reader.SkipOpenParenthesis(); string protection; + string protectedAt = null; S2k s2k; byte[] iv; byte[] secKeyData; @@ -1312,26 +1320,42 @@ private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool reader.SkipCloseParenthesis(); secKeyData = reader.ReadBytes(); + + reader.SkipCloseParenthesis(); + + reader.SkipOpenParenthesis(); + + if (reader.ReadString().Equals("protected-at")) + { + protectedAt = reader.ReadString(); + } } else { throw new PgpException("protected block not found"); } - // Valid values of protection: openpgp-s2k3-sha1-aes-cbc, openpgp-s2k3-ocb-aes, openpgp-native byte[] data; KeyParameter key; switch (protection) { + case "openpgp-s2k3-sha1-aes256-cbc": case "openpgp-s2k3-sha1-aes-cbc": - key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); - data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + SymmetricKeyAlgorithmTag symmAlg = + protection.Equals("openpgp-s2k3-sha1-aes256-cbc") ? SymmetricKeyAlgorithmTag.Aes256 : SymmetricKeyAlgorithmTag.Aes128; + key = PgpUtilities.DoMakeKeyFromPassPhrase(symmAlg, s2k, rawPassPhrase, clearPassPhrase); + data = RecoverKeyData(symmAlg, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + // TODO: check SHA-1 hash. break; case "openpgp-s2k3-ocb-aes": + MemoryStream aad = new MemoryStream(); + WriteSExprPublicKey(new SXprWriter(aad), publicKey, curveName, protectedAt); key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); - data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/OCB/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + IBufferedCipher c = CipherUtilities.GetCipher("AES/OCB"); + c.Init(false, new AeadParameters(key, 128, iv, aad.ToArray())); + data = c.DoFinal(secKeyData, 0, secKeyData.Length); break; case "openpgp-native": @@ -1344,7 +1368,7 @@ private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool // Stream keyIn = new MemoryStream(data, false); - reader = new SXprUtilities(keyIn); + reader = new SXprReader(keyIn); reader.SkipOpenParenthesis(); reader.SkipOpenParenthesis(); reader.SkipOpenParenthesis(); diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprReader.cs similarity index 97% rename from crypto/src/openpgp/SXprUtilities.cs rename to crypto/src/openpgp/SXprReader.cs index 7cd4862065..7e6d15b341 100644 --- a/crypto/src/openpgp/SXprUtilities.cs +++ b/crypto/src/openpgp/SXprReader.cs @@ -7,19 +7,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { /** - * Utility functions for looking a S-expression keys. This class will move when it finds a better home! + * Reader for S-expression keys. This class will move when it finds a better home! * <p> * Format documented here: * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master * http://people.csail.mit.edu/rivest/Sexp.txt * </p> */ - class SXprUtilities + class SXprReader { Stream stream; int peekedByte; - internal SXprUtilities(Stream stream) + public SXprReader(Stream stream) { this.stream = stream; this.peekedByte = -1; @@ -211,7 +211,7 @@ public S2k ParseS2k() return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); } - private void SkipWhitespace() + public void SkipWhitespace() { int ch = ReadByte(); while (ch == ' ' || ch == '\r' || ch == '\n') diff --git a/crypto/src/openpgp/SXprWriter.cs b/crypto/src/openpgp/SXprWriter.cs new file mode 100644 index 0000000000..15ee461644 --- /dev/null +++ b/crypto/src/openpgp/SXprWriter.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using System.Text; + +namespace crypto.openpgp +{ + /** + * Writer for S-expression keys + * <p> + * Format documented here: + * http://people.csail.mit.edu/rivest/Sexp.txt + * + * Only canonical S expression format is used. + * </p> + */ + class SXprWriter + { + Stream output; + + public SXprWriter(Stream output) + { + this.output = output; + } + + public void StartList() + { + output.WriteByte((byte)'('); + } + + public void EndList() + { + output.WriteByte((byte)')'); + } + + public void WriteString(string s) + { + byte[] stringBytes = Encoding.UTF8.GetBytes(s); + byte[] lengthBytes = Encoding.UTF8.GetBytes(stringBytes.Length + ":"); + output.Write(lengthBytes, 0, lengthBytes.Length); + output.Write(stringBytes, 0, stringBytes.Length); + } + + public void WriteBytes(byte[] b) + { + byte[] lengthBytes = Encoding.UTF8.GetBytes(b.Length + ":"); + output.Write(lengthBytes, 0, lengthBytes.Length); + output.Write(b, 0, b.Length); + } + } +} diff --git a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs index a670b14902..3a9d6c46b2 100644 --- a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs +++ b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs @@ -184,8 +184,7 @@ public override void PerformTest() // // sExpr // - // TODO: Fails the OCB MAC check when decoding key but works otherwise - /*byte[] msg = Encoding.ASCII.GetBytes("hello world!"); + byte[] msg = Encoding.ASCII.GetBytes("hello world!"); PgpSecretKey key = PgpSecretKey.ParseSecretKeyFromSExpr(new MemoryStream(sExprKey, false), "test".ToCharArray()); @@ -200,7 +199,7 @@ public override void PerformTest() if (!sig.Verify()) { Fail("signature failed to verify!"); - }*/ + } } private static object First(IEnumerable e) From 5fba3f4a6ee79f2e7333b59c84973f6e79a4fed2 Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Sun, 13 Dec 2020 13:03:09 +0100 Subject: [PATCH 4/8] Fix endianness issue and key generation in ECDH --- crypto/src/bcpg/ECDHPublicBCPGKey.cs | 16 ++ .../src/openpgp/PgpEncryptedDataGenerator.cs | 10 +- crypto/src/openpgp/PgpPublicKey.cs | 20 ++- .../src/openpgp/PgpPublicKeyEncryptedData.cs | 9 +- crypto/src/openpgp/PgpSecretKey.cs | 24 ++- crypto/test/src/openpgp/test/PgpECDHTest.cs | 151 +++++++++++++++++- 6 files changed, 212 insertions(+), 18 deletions(-) diff --git a/crypto/src/bcpg/ECDHPublicBCPGKey.cs b/crypto/src/bcpg/ECDHPublicBCPGKey.cs index dc225e31e1..5c6785d261 100644 --- a/crypto/src/bcpg/ECDHPublicBCPGKey.cs +++ b/crypto/src/bcpg/ECDHPublicBCPGKey.cs @@ -1,6 +1,7 @@ using System; using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; namespace Org.BouncyCastle.Bcpg @@ -48,6 +49,21 @@ public ECDHPublicBcpgKey( VerifySymmetricKeyAlgorithm(); } + public ECDHPublicBcpgKey( + DerObjectIdentifier oid, + BigInteger encodedPoint, + HashAlgorithmTag hashAlgorithm, + SymmetricKeyAlgorithmTag symmetricKeyAlgorithm) + : base(oid, encodedPoint) + { + reserved = 1; + hashFunctionId = hashAlgorithm; + symAlgorithmId = symmetricKeyAlgorithm; + + VerifyHashAlgorithm(); + VerifySymmetricKeyAlgorithm(); + } + public virtual byte Reserved { get { return reserved; } diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs index 81847cabd3..bd8f14d524 100644 --- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs +++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs @@ -104,6 +104,8 @@ public override void AddSessionInfo( private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) { + AsymmetricKeyParameter akp = pubKey.GetKey(); + if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH) { IBufferedCipher c; @@ -127,7 +129,6 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm); } - AsymmetricKeyParameter akp = pubKey.GetKey(); c.Init(true, new ParametersWithRandom(akp, random)); return c.DoFinal(sessionInfo); } @@ -136,12 +137,13 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) KeyParameter key; byte[] encodedPublicKey; - if (ecKey.CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) + if (akp is X25519PublicKeyParameters) { + X25519PublicKeyParameters pub = (X25519PublicKeyParameters)akp; byte[] privateKey = new byte[X25519.PointSize]; X25519.GeneratePrivateKey(random, privateKey); byte[] sharedKey = new byte[32]; - X25519.CalculateAgreement(privateKey, 0, BigIntegers.AsUnsignedByteArray(ecKey.EncodedPoint), 1, sharedKey, 0); + X25519.CalculateAgreement(privateKey, 0, pub.GetEncoded(), 0, sharedKey, 0); byte[] publicKey = new byte[X25519.PointSize + 1]; publicKey[0] = 0x40; // compressed point X25519.GeneratePublicKey(privateKey, 0, publicKey, 1); @@ -158,7 +160,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private; ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public; - ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey(); + ECPublicKeyParameters pub = (ECPublicKeyParameters)akp; ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize(); key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S)); diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index 0a5137d3ad..f13a392b5d 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -11,6 +11,7 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Rfc7748; using Org.BouncyCastle.Math.EC.Rfc8032; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; @@ -196,6 +197,19 @@ public PgpPublicKey( ecK.Encode(encodedPoint, 1); bcpgKey = new ECDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, encodedPoint)); } + else if (pubKey is X25519PublicKeyParameters) + { + X25519PublicKeyParameters ecK = (X25519PublicKeyParameters)pubKey; + byte[] encodedPoint = new byte[X25519.PointSize + 1]; + encodedPoint[0] = 0x40; + ecK.Encode(encodedPoint, 1); + Array.Reverse(encodedPoint, 1, X25519.PointSize); + bcpgKey = new ECDHPublicBcpgKey( + MiscObjectIdentifiers.Curve25519, + new BigInteger(1, encodedPoint), + HashAlgorithmTag.Sha256, + SymmetricKeyAlgorithmTag.Aes128); + } else if (pubKey is ElGamalPublicKeyParameters) { ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey; @@ -510,7 +524,11 @@ public AsymmetricKeyParameter GetKey() return GetECKey("ECDSA"); case PublicKeyAlgorithmTag.ECDH: if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) - return new X25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 0); + { + byte[] encodedPoint = ((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(); + Array.Reverse(encodedPoint, 1, X25519.PointSize); + return new X25519PublicKeyParameters(encodedPoint, 1); + } else return GetECKey("ECDH"); case PublicKeyAlgorithmTag.EdDsa: diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs index be05f36fe4..0c47af3c3e 100644 --- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs +++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs @@ -217,11 +217,10 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey) if (privKey.Key is X25519PrivateKeyParameters x25519privKeyParams) { - byte[] sharedKey = new byte[32]; - byte[] reversedPrivateKey = new byte[32]; - x25519privKeyParams.Encode(reversedPrivateKey, 0); - Array.Reverse((Array)reversedPrivateKey); - X25519.ScalarMult(reversedPrivateKey, 0, pEnc, 1, sharedKey, 0); + byte[] sharedKey = new byte[X25519.PointSize]; + byte[] privateKey = new byte[X25519.PointSize]; + x25519privKeyParams.Encode(privateKey, 0); + X25519.CalculateAgreement(privateKey, 0, pEnc, 1, sharedKey, 0); key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, sharedKey)); } else diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 4c44fbdf57..1eefc2f1d3 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -57,9 +57,23 @@ internal PgpSecretKey( secKey = new DsaSecretBcpgKey(dsK.X); break; case PublicKeyAlgorithmTag.ECDH: + if (privKey.Key is X25519PrivateKeyParameters) + { + X25519PrivateKeyParameters x25519K = (X25519PrivateKeyParameters)privKey.Key; + byte[] secretKey = new byte[X25519PrivateKeyParameters.KeySize]; + x25519K.Encode(secretKey, 0); + Array.Reverse(secretKey); + secKey = new ECSecretBcpgKey(new BigInteger(1, secretKey)); + } + else + { + ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key; + secKey = new ECSecretBcpgKey(ecK.D); + } + break; case PublicKeyAlgorithmTag.ECDsa: - ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key; - secKey = new ECSecretBcpgKey(ecK.D); + ECPrivateKeyParameters ecdsaK = (ECPrivateKeyParameters)privKey.Key; + secKey = new ECSecretBcpgKey(ecdsaK.D); break; case PublicKeyAlgorithmTag.EdDsa: Ed25519PrivateKeyParameters edK = (Ed25519PrivateKeyParameters)privKey.Key; @@ -683,7 +697,11 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP break; case PublicKeyAlgorithmTag.ECDH: if (((ECPublicBcpgKey)secret.PublicKeyPacket.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) - privateKey = new X25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0); + { + var x = new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(); + Array.Reverse(x); + privateKey = new X25519PrivateKeyParameters(x, 0); + } else privateKey = GetECKey("ECDH", bcpgIn); break; diff --git a/crypto/test/src/openpgp/test/PgpECDHTest.cs b/crypto/test/src/openpgp/test/PgpECDHTest.cs index 94ab2a755e..4f7c372401 100644 --- a/crypto/test/src/openpgp/test/PgpECDHTest.cs +++ b/crypto/test/src/openpgp/test/PgpECDHTest.cs @@ -4,13 +4,16 @@ using System.Text; using NUnit.Framework; - +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO; using Org.BouncyCastle.Utilities.Test; namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests @@ -51,6 +54,40 @@ public class PgpECDHTest "6HiuFH7VKWcxPUBjXwf5+Z3uOKEp28tBgNyDrdbr1BbqlgYzIKq/pe9zUbUXfitn" + "vFc6HcGhvmRQreQ+Yw1x3x0HJeoPwg=="); + private static readonly byte[] testX25519PubKey = + Base64.Decode( + "mDMEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" + + "L+yb4A20HEVDREggPHRlc3QuZWNkaEBleGFtcGxlLmNvbT6IkAQTFggAOBYhBGoy" + + "UrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B" + + "AheAAAoJEDewWiN8tfzX0ZMA/AhEvrIgu+29eMQeuHOwX1ZY/UssU5TdVROQzGTL" + + "n5cgAP9hIKtt/mZ112HiAHDuWk2JskdtsuopnrEccz4PSEkSDLg4BF/V8F4SCisG" + + "AQQBl1UBBQEBB0DLPhNt/6GHDbb7vZW/iMsbXTZpgJNQiT6QA/4EzgYQLwMBCAeI" + + "eAQYFggAIBYhBGoyUrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsMAAoJEDewWiN8" + + "tfzXU34BAKJJLDee+qJCmUI20sMy/YoKfWmMnH2RBBHmLV8FAJ7vAP0e2wGixEfs" + + "oPqe8fHmvjQGxSByOyQGn7yD+oq9nVzTAA=="); + + private static readonly byte[] testX25519PrivKey = + Base64.Decode( + "lIYEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" + + "L+yb4A3+BwMCMscozrXr93fOFmtxu/BJjEJrwRl20Jrv9lryfM+SF4UHgVMmJUpJ" + + "1RuTbSnM2KaqHwOgmdrvf2FJnpg1vMafBk1CmopqkRzzrbJ6xQhiPrQcRUNESCA8" + + "dGVzdC5lY2RoQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEEajJSvE2/tzdLYmMbN7Ba" + + "I3y1/NcFAl/V8F4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQN7BaI3y1" + + "/NfRkwD8CES+siC77b14xB64c7BfVlj9SyxTlN1VE5DMZMuflyAA/2Egq23+ZnXX" + + "YeIAcO5aTYmyR22y6imesRxzPg9ISRIMnIsEX9XwXhIKKwYBBAGXVQEFAQEHQMs+" + + "E23/oYcNtvu9lb+IyxtdNmmAk1CJPpAD/gTOBhAvAwEIB/4HAwJ7ShSBrUuUAM5r" + + "G4I/gJKo+eBmbNC4NM81eALAF1vcovZPsGsiZ8IgXT64XiC1bpeAoINn6vM4vVbi" + + "LqNKqu6ll3ZgQ4po6vCW9GkhuEMmiHgEGBYIACAWIQRqMlK8Tb+3N0tiYxs3sFoj" + + "fLX81wUCX9XwXgIbDAAKCRA3sFojfLX811N+AQCiSSw3nvqiQplCNtLDMv2KCn1p" + + "jJx9kQQR5i1fBQCe7wD9HtsBosRH7KD6nvHx5r40BsUgcjskBp+8g/qKvZ1c0wA="); + + private static readonly byte[] testX25519Message = + Base64.Decode( + "hF4DbDc2fNL0VcUSAQdAqdV0v1D4X9cuGrT7+oQBpMFnw1wdfAcxH9xdO00s2HUw" + + "qB+XkIRETH7yesynLOKajmYftMWZRyTnW2tJUc1w5NFPjPxcbvd2bYmqkY57uAFg" + + "0kcBKhFklH2LRbBNThtQr3jn2YEFbNnhiGfOpoHfCn0oFh5RbXDwm+P3Q3tksvpZ" + + "wEGe2VkxLLe7BWnv/sRINQ2YpuaYshe8hw=="); + private void Generate() { SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); @@ -105,6 +142,71 @@ private void Generate() PgpPrivateKey pgpPrivKey = secRing.GetSecretKey().ExtractPrivateKey(passPhrase); } + private void Generate25519() + { + SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); + + // + // Generate a master key + // + IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519"); + keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random)); + + AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair(); + + PgpKeyPair ecdsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow); + + // + // Generate an encryption key + // + keyGen = GeneratorUtilities.GetKeyPairGenerator("X25519"); + keyGen.Init(new ECKeyGenerationParameters(MiscObjectIdentifiers.Curve25519, random)); + + AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair(); + + PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow); + + // + // Generate a key ring + // + char[] passPhrase = "test".ToCharArray(); + PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, ecdsaKeyPair, + "test@bouncycastle.org", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random); + keyRingGen.AddSubKey(ecdhKeyPair); + + PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing(); + + // TODO: add check of KdfParameters + DoBasicKeyRingCheck(pubRing); + + PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing(); + + PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded()); + if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded())) + { + Fail("public key ring encoding failed"); + } + + PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded()); + if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded())) + { + Fail("secret key ring encoding failed"); + } + + // Extract back the ECDH key and verify the encoded values to ensure correct endianness + PgpSecretKey pgpSecretKey = secRing.GetSecretKey(ecdhKeyPair.KeyId); + PgpPrivateKey pgpPrivKey = pgpSecretKey.ExtractPrivateKey(passPhrase); + + if (!Arrays.AreEqual(((X25519PrivateKeyParameters)kpEnc.Private).GetEncoded(), ((X25519PrivateKeyParameters)pgpPrivKey.Key).GetEncoded())) + { + Fail("private key round trip failed"); + } + if (!Arrays.AreEqual(((X25519PublicKeyParameters)kpEnc.Public).GetEncoded(), ((X25519PublicKeyParameters)pgpSecretKey.PublicKey.GetKey()).GetEncoded())) + { + Fail("private key round trip failed"); + } + } + private void TestDecrypt(PgpSecretKeyRing secretKeyRing) { PgpObjectFactory pgpF = new PgpObjectFactory(testMessage); @@ -134,14 +236,14 @@ private void TestDecrypt(PgpSecretKeyRing secretKeyRing) // } } - private void EncryptDecryptTest() + private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve) { SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); byte[] text = Encoding.ASCII.GetBytes("hello world!"); - IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("ECDH"); - keyGen.Init(new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random)); + IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm); + keyGen.Init(new ECKeyGenerationParameters(curve, random)); AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair(); @@ -197,6 +299,39 @@ private void EncryptDecryptTest() } } + private void GnuPGCrossCheck() + { + PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey); + + PgpObjectFactory pgpF = new PgpObjectFactory(testX25519Message); + + PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject(); + + PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0]; + + PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5); + + PgpPrivateKey pgpPrivKey = secretKey.ExtractPrivateKey("test".ToCharArray()); + + Stream clear = encP.GetDataStream(pgpPrivKey); + + pgpF = new PgpObjectFactory(clear); + + PgpCompressedData c1 = (PgpCompressedData)pgpF.NextPgpObject(); + + pgpF = new PgpObjectFactory(c1.GetDataStream()); + + PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject(); + + Stream inLd = ld.GetDataStream(); + byte[] bytes = Streams.ReadAll(inLd); + + if (!Arrays.AreEqual(bytes, Encoding.ASCII.GetBytes("hello world!"))) + { + Fail("wrong plain text in decrypted packet"); + } + } + public override void PerformTest() { // @@ -213,9 +348,15 @@ public override void PerformTest() TestDecrypt(secretKeyRing); - EncryptDecryptTest(); + EncryptDecryptTest("ECDH", SecObjectIdentifiers.SecP256r1); + + EncryptDecryptTest("X25519", MiscObjectIdentifiers.Curve25519); + + GnuPGCrossCheck(); Generate(); + + Generate25519(); } private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing) From b4aed35b783590dbb3f372d4f87288b1f9b5d903 Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Sun, 13 Dec 2020 13:41:22 +0100 Subject: [PATCH 5/8] Add roundtrip test for X25519 with GnuPG keys --- crypto/src/openpgp/PgpPublicKey.cs | 2 - crypto/test/src/openpgp/test/PgpECDHTest.cs | 71 +++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index f13a392b5d..462f17e037 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -203,7 +203,6 @@ public PgpPublicKey( byte[] encodedPoint = new byte[X25519.PointSize + 1]; encodedPoint[0] = 0x40; ecK.Encode(encodedPoint, 1); - Array.Reverse(encodedPoint, 1, X25519.PointSize); bcpgKey = new ECDHPublicBcpgKey( MiscObjectIdentifiers.Curve25519, new BigInteger(1, encodedPoint), @@ -526,7 +525,6 @@ public AsymmetricKeyParameter GetKey() if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id)) { byte[] encodedPoint = ((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(); - Array.Reverse(encodedPoint, 1, X25519.PointSize); return new X25519PublicKeyParameters(encodedPoint, 1); } else diff --git a/crypto/test/src/openpgp/test/PgpECDHTest.cs b/crypto/test/src/openpgp/test/PgpECDHTest.cs index 4f7c372401..9c307cb353 100644 --- a/crypto/test/src/openpgp/test/PgpECDHTest.cs +++ b/crypto/test/src/openpgp/test/PgpECDHTest.cs @@ -299,6 +299,75 @@ private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve) } } + + private void EncryptDecryptX25519KeysTest() + { + SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); + + /*IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm); + keyGen.Init(new ECKeyGenerationParameters(curve, random)); + + AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair(); + + PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow);*/ + PgpPublicKeyRing publicKeyRing = new PgpPublicKeyRing(testX25519PubKey); + + PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey); + + PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5); + + byte[] text = Encoding.ASCII.GetBytes("hello world!"); + + PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator(); + MemoryStream ldOut = new MemoryStream(); + Stream pOut = lData.Open(ldOut, PgpLiteralDataGenerator.Utf8, PgpLiteralData.Console, text.Length, DateTime.UtcNow); + + pOut.Write(text, 0, text.Length); + + pOut.Close(); + + byte[] data = ldOut.ToArray(); + + MemoryStream cbOut = new MemoryStream(); + + PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, random); + cPk.AddMethod(publicKeyRing.GetPublicKey(0x6c37367cd2f455c5)); + + Stream cOut = cPk.Open(new UncloseableStream(cbOut), data.Length); + + cOut.Write(data, 0, data.Length); + + cOut.Close(); + + PgpObjectFactory pgpF = new PgpObjectFactory(cbOut.ToArray()); + + PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject(); + + PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0]; + + Stream clear = encP.GetDataStream(secretKey.ExtractPrivateKey("test".ToCharArray())); + + pgpF = new PgpObjectFactory(clear); + + PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject(); + + clear = ld.GetInputStream(); + MemoryStream bOut = new MemoryStream(); + + int ch; + while ((ch = clear.ReadByte()) >= 0) + { + bOut.WriteByte((byte)ch); + } + + byte[] output = bOut.ToArray(); + + if (!AreEqual(output, text)) + { + Fail("wrong plain text in Generated packet"); + } + } + private void GnuPGCrossCheck() { PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey); @@ -357,6 +426,8 @@ public override void PerformTest() Generate(); Generate25519(); + + EncryptDecryptX25519KeysTest(); } private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing) From 31c46556f41aa10ac93ab122c591a048dae150c5 Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Wed, 23 Jun 2021 14:48:30 +0200 Subject: [PATCH 6/8] Fix namespace --- crypto/src/openpgp/PgpSecretKey.cs | 1 - crypto/src/openpgp/SXprWriter.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 1eefc2f1d3..134fdf87dc 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Diagnostics; using System.IO; -using crypto.openpgp; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.X9; diff --git a/crypto/src/openpgp/SXprWriter.cs b/crypto/src/openpgp/SXprWriter.cs index 15ee461644..640c4b6d6e 100644 --- a/crypto/src/openpgp/SXprWriter.cs +++ b/crypto/src/openpgp/SXprWriter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text; -namespace crypto.openpgp +namespace Org.BouncyCastle.Bcpg.OpenPgp { /** * Writer for S-expression keys From be112f4faccc06d58a850387998e0b08aab0fb28 Mon Sep 17 00:00:00 2001 From: Filip Navara <navara@emclient.com> Date: Wed, 23 Jun 2021 15:00:11 +0200 Subject: [PATCH 7/8] Fix build --- crypto/BouncyCastle.Android.csproj | 4 +- crypto/BouncyCastle.csproj | 4 +- crypto/BouncyCastle.iOS.csproj | 4 +- crypto/crypto.csproj | 12 ++++- .../src/crypto/signers/EdDsa255519Signer.cs | 47 ------------------- 5 files changed, 20 insertions(+), 51 deletions(-) delete mode 100644 crypto/src/crypto/signers/EdDsa255519Signer.cs diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj index 592dcc977a..b9d30e2d89 100644 --- a/crypto/BouncyCastle.Android.csproj +++ b/crypto/BouncyCastle.Android.csproj @@ -1057,6 +1057,7 @@ <Compile Include="src\crypto\signers\Ed25519Signer.cs" /> <Compile Include="src\crypto\signers\Ed448phSigner.cs" /> <Compile Include="src\crypto\signers\Ed448Signer.cs" /> + <Compile Include="src\crypto\signers\EdDsa25519Signer.cs" /> <Compile Include="src\crypto\signers\GOST3410DigestSigner.cs" /> <Compile Include="src\crypto\signers\GOST3410Signer.cs" /> <Compile Include="src\crypto\signers\GenericSigner.cs" /> @@ -1486,7 +1487,8 @@ <Compile Include="src\openpgp\PgpUtilities.cs" /> <Compile Include="src\openpgp\PgpV3SignatureGenerator.cs" /> <Compile Include="src\openpgp\Rfc6637Utilities.cs" /> - <Compile Include="src\openpgp\SXprUtilities.cs" /> + <Compile Include="src\openpgp\SXprReader.cs" /> + <Compile Include="src\openpgp\SXprWriter.cs" /> <Compile Include="src\openpgp\WrappedGeneratorStream.cs" /> <Compile Include="src\openssl\EncryptionException.cs" /> <Compile Include="src\openssl\IPasswordFinder.cs" /> diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj index 26db97e1b9..709f383567 100644 --- a/crypto/BouncyCastle.csproj +++ b/crypto/BouncyCastle.csproj @@ -1051,6 +1051,7 @@ <Compile Include="src\crypto\signers\Ed25519Signer.cs" /> <Compile Include="src\crypto\signers\Ed448phSigner.cs" /> <Compile Include="src\crypto\signers\Ed448Signer.cs" /> + <Compile Include="src\crypto\signers\EdDsa25519Signer.cs" /> <Compile Include="src\crypto\signers\GOST3410DigestSigner.cs" /> <Compile Include="src\crypto\signers\GOST3410Signer.cs" /> <Compile Include="src\crypto\signers\GenericSigner.cs" /> @@ -1480,7 +1481,8 @@ <Compile Include="src\openpgp\PgpUtilities.cs" /> <Compile Include="src\openpgp\PgpV3SignatureGenerator.cs" /> <Compile Include="src\openpgp\Rfc6637Utilities.cs" /> - <Compile Include="src\openpgp\SXprUtilities.cs" /> + <Compile Include="src\openpgp\SXprReader.cs" /> + <Compile Include="src\openpgp\SXprWriter.cs" /> <Compile Include="src\openpgp\WrappedGeneratorStream.cs" /> <Compile Include="src\openssl\EncryptionException.cs" /> <Compile Include="src\openssl\IPasswordFinder.cs" /> diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj index 17fe3dda53..d6b2fc7461 100644 --- a/crypto/BouncyCastle.iOS.csproj +++ b/crypto/BouncyCastle.iOS.csproj @@ -1052,6 +1052,7 @@ <Compile Include="src\crypto\signers\Ed25519Signer.cs" /> <Compile Include="src\crypto\signers\Ed448phSigner.cs" /> <Compile Include="src\crypto\signers\Ed448Signer.cs" /> + <Compile Include="src\crypto\signers\EdDsa25519Signer.cs" /> <Compile Include="src\crypto\signers\GOST3410DigestSigner.cs" /> <Compile Include="src\crypto\signers\GOST3410Signer.cs" /> <Compile Include="src\crypto\signers\GenericSigner.cs" /> @@ -1481,7 +1482,8 @@ <Compile Include="src\openpgp\PgpUtilities.cs" /> <Compile Include="src\openpgp\PgpV3SignatureGenerator.cs" /> <Compile Include="src\openpgp\Rfc6637Utilities.cs" /> - <Compile Include="src\openpgp\SXprUtilities.cs" /> + <Compile Include="src\openpgp\SXprReader.cs" /> + <Compile Include="src\openpgp\SXprWriter.cs" /> <Compile Include="src\openpgp\WrappedGeneratorStream.cs" /> <Compile Include="src\openssl\EncryptionException.cs" /> <Compile Include="src\openssl\IPasswordFinder.cs" /> diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index 1ba78a83c4..95e0a3b3c3 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -5143,6 +5143,11 @@ SubType = "Code" BuildAction = "Compile" /> + <File + RelPath = "src\crypto\signers\EdDsa25519Signer.cs" + SubType = "Code" + BuildAction = "Compile" + /> <File RelPath = "src\crypto\signers\GenericSigner.cs" SubType = "Code" @@ -7289,7 +7294,12 @@ BuildAction = "Compile" /> <File - RelPath = "src\openpgp\SXprUtilities.cs" + RelPath = "src\openpgp\SXprReader.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\openpgp\SXprWriter.cs" SubType = "Code" BuildAction = "Compile" /> diff --git a/crypto/src/crypto/signers/EdDsa255519Signer.cs b/crypto/src/crypto/signers/EdDsa255519Signer.cs deleted file mode 100644 index 345f5ddb23..0000000000 --- a/crypto/src/crypto/signers/EdDsa255519Signer.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Crypto.Signers -{ - public class EdDsa22519Signer : IDsa - { - private Ed25519Signer signer; - - public virtual string AlgorithmName => "EDDSA"; - - public EdDsa22519Signer() - { - signer = new Ed25519Signer(); - } - - public virtual void Init(bool forSigning, ICipherParameters parameters) - { - signer.Init(forSigning, parameters); - } - - public virtual BigInteger[] GenerateSignature(byte[] message) - { - signer.BlockUpdate(message, 0, message.Length); - byte[] sigBytes = signer.GenerateSignature(); - byte[] rBytes = new byte[32]; - Array.Copy(sigBytes, rBytes, 32); - byte[] sBytes = new byte[sigBytes.Length - 32]; - Array.Copy(sigBytes, 32, sBytes, 0, sigBytes.Length - 32); - BigInteger r = new BigInteger(1, rBytes); - BigInteger s = new BigInteger(1, sBytes); - return new BigInteger[2] { r, s }; - } - - public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s) - { - signer.BlockUpdate(message, 0, message.Length); - byte[] rBytes = r.ToByteArrayUnsigned(); - byte[] sBytes = s.ToByteArrayUnsigned(); - byte[] sigBytes = new byte[rBytes.Length + sBytes.Length]; - Array.Copy(rBytes, sigBytes, rBytes.Length); - Array.Copy(sBytes, 0, sigBytes, rBytes.Length, sBytes.Length); - return signer.VerifySignature(sigBytes); - } - } -} From 8cbf92dd15ea27d8f6c4c89518543d2f08a1e6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Tom=C3=A1=C5=A1ek?= <tomasek@emclient.com> Date: Wed, 14 Jul 2021 13:24:33 +0200 Subject: [PATCH 8/8] Add missing file Fixed signature bytes array length for EdDSA22519 --- crypto/src/crypto/signers/EdDsa25519Signer.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 crypto/src/crypto/signers/EdDsa25519Signer.cs diff --git a/crypto/src/crypto/signers/EdDsa25519Signer.cs b/crypto/src/crypto/signers/EdDsa25519Signer.cs new file mode 100644 index 0000000000..8a8eb0c9fb --- /dev/null +++ b/crypto/src/crypto/signers/EdDsa25519Signer.cs @@ -0,0 +1,47 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class EdDsa22519Signer : IDsa + { + private Ed25519Signer signer; + + public virtual string AlgorithmName => "EDDSA"; + + public EdDsa22519Signer() + { + signer = new Ed25519Signer(); + } + + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + signer.Init(forSigning, parameters); + } + + public virtual BigInteger[] GenerateSignature(byte[] message) + { + signer.BlockUpdate(message, 0, message.Length); + byte[] sigBytes = signer.GenerateSignature(); + byte[] rBytes = new byte[32]; + Array.Copy(sigBytes, rBytes, 32); + byte[] sBytes = new byte[sigBytes.Length - 32]; + Array.Copy(sigBytes, 32, sBytes, 0, sigBytes.Length - 32); + BigInteger r = new BigInteger(1, rBytes); + BigInteger s = new BigInteger(1, sBytes); + return new BigInteger[2] { r, s }; + } + + public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s) + { + signer.BlockUpdate(message, 0, message.Length); + byte[] rBytes = r.ToByteArrayUnsigned(); + byte[] sBytes = s.ToByteArrayUnsigned(); + byte[] sigBytes = new byte[64]; + Array.Copy(rBytes, 0, sigBytes, 32 - rBytes.Length, rBytes.Length); + Array.Copy(sBytes, 0, sigBytes, 64 - sBytes.Length, sBytes.Length); + return signer.VerifySignature(sigBytes); + } + } +}