From 7f43ba84bd30b99d0e957920aa660a45f998c522 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 5 Nov 2022 15:40:09 +0700 Subject: Port OpenPGP support for XDH, EdDSA from bc-java - see https://github.com/bcgit/bc-csharp/issues/345 --- crypto/src/openpgp/EdDsaSigner.cs | 72 ++++++ crypto/src/openpgp/PgpEncryptedDataGenerator.cs | 129 +++++++--- crypto/src/openpgp/PgpKdfParameters.cs | 21 ++ crypto/src/openpgp/PgpObjectFactory.cs | 5 +- crypto/src/openpgp/PgpOnePassSignature.cs | 78 +++--- crypto/src/openpgp/PgpPbeEncryptedData.cs | 5 +- crypto/src/openpgp/PgpPublicKey.cs | 311 +++++++++++++++++------- crypto/src/openpgp/PgpPublicKeyEncryptedData.cs | 156 +++++++----- crypto/src/openpgp/PgpPublicKeyRing.cs | 26 +- crypto/src/openpgp/PgpSecretKey.cs | 105 +++++++- crypto/src/openpgp/PgpSecretKeyRing.cs | 47 +++- crypto/src/openpgp/PgpSignature.cs | 151 +++++++----- crypto/src/openpgp/PgpSignatureGenerator.cs | 193 ++++++++------- crypto/src/openpgp/PgpUtilities.cs | 57 ++++- crypto/src/openpgp/PgpV3SignatureGenerator.cs | 105 ++++---- crypto/src/openpgp/Rfc6637Utilities.cs | 14 +- 16 files changed, 1012 insertions(+), 463 deletions(-) create mode 100644 crypto/src/openpgp/EdDsaSigner.cs create mode 100644 crypto/src/openpgp/PgpKdfParameters.cs (limited to 'crypto/src/openpgp') diff --git a/crypto/src/openpgp/EdDsaSigner.cs b/crypto/src/openpgp/EdDsaSigner.cs new file mode 100644 index 000000000..0e15ac609 --- /dev/null +++ b/crypto/src/openpgp/EdDsaSigner.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + internal sealed class EdDsaSigner + : ISigner + { + private readonly ISigner m_signer; + private readonly IDigest m_digest; + private readonly byte[] m_digBuf; + + internal EdDsaSigner(ISigner signer, IDigest digest) + { + m_signer = signer; + m_digest = digest; + m_digBuf = new byte[digest.GetDigestSize()]; + } + + public string AlgorithmName => m_signer.AlgorithmName; + + public void Init(bool forSigning, ICipherParameters cipherParameters) + { + m_signer.Init(forSigning, cipherParameters); + m_digest.Reset(); + } + + public void Update(byte b) + { + m_digest.Update(b); + } + + public void BlockUpdate(byte[] input, int inOff, int inLen) + { + m_digest.BlockUpdate(input, inOff, inLen); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void BlockUpdate(ReadOnlySpan input) + { + m_digest.BlockUpdate(input); + } +#endif + + public byte[] GenerateSignature() + { + m_digest.DoFinal(m_digBuf, 0); + + m_signer.BlockUpdate(m_digBuf, 0, m_digBuf.Length); + + return m_signer.GenerateSignature(); + } + + public bool VerifySignature(byte[] signature) + { + m_digest.DoFinal(m_digBuf, 0); + + m_signer.BlockUpdate(m_digBuf, 0, m_digBuf.Length); + + return m_signer.VerifySignature(signature); + } + + public void Reset() + { + Arrays.Clear(m_digBuf); + m_signer.Reset(); + m_digest.Reset(); + } + } +} diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs index 589895522..69e0d5dbc 100644 --- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs +++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs @@ -2,17 +2,21 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; - +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cryptlib; +using Org.BouncyCastle.Asn1.EdEC; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.IO; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { - /// Generator for encrypted objects. + /// Generator for encrypted objects. public class PgpEncryptedDataGenerator : IStreamGenerator { @@ -99,54 +103,108 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) { + var cryptoPublicKey = pubKey.GetKey(); + if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH) { IBufferedCipher c; switch (pubKey.Algorithm) { - case PublicKeyAlgorithmTag.RsaEncrypt: - case PublicKeyAlgorithmTag.RsaGeneral: - c = CipherUtilities.GetCipher("RSA//PKCS1Padding"); - break; - case PublicKeyAlgorithmTag.ElGamalEncrypt: - case PublicKeyAlgorithmTag.ElGamalGeneral: - c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding"); - break; - case PublicKeyAlgorithmTag.Dsa: - throw new PgpException("Can't use DSA for encryption."); - case PublicKeyAlgorithmTag.ECDsa: - throw new PgpException("Can't use ECDSA for encryption."); - default: - throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm); + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + c = CipherUtilities.GetCipher("RSA//PKCS1Padding"); + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding"); + break; + case PublicKeyAlgorithmTag.Dsa: + 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(cryptoPublicKey, random)); return c.DoFinal(sessionInfo); } - ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key; + ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key; + var curveOid = ecPubKey.CurveOid; + + if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) || + CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)) + { + X25519KeyPairGenerator gen = new X25519KeyPairGenerator(); + gen.Init(new X25519KeyGenerationParameters(random)); + + AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); + + X25519Agreement agreement = new X25519Agreement(); + agreement.Init(ephKp.Private); + + byte[] secret = new byte[agreement.AgreementSize]; + agreement.CalculateAgreement(cryptoPublicKey, secret, 0); + + byte[] ephPubEncoding = new byte[1 + X25519PublicKeyParameters.KeySize]; + ephPubEncoding[0] = 0x40; + ((X25519PublicKeyParameters)ephKp.Public).Encode(ephPubEncoding, 1); + + return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random); + } + else if (EdECObjectIdentifiers.id_X448.Equals(curveOid)) + { + X448KeyPairGenerator gen = new X448KeyPairGenerator(); + gen.Init(new X448KeyGenerationParameters(random)); + + AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); + + X448Agreement agreement = new X448Agreement(); + agreement.Init(ephKp.Private); + + byte[] secret = new byte[agreement.AgreementSize]; + agreement.CalculateAgreement(cryptoPublicKey, secret, 0); - // Generate the ephemeral key pair - IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH"); - gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random)); + byte[] ephPubEncoding = new byte[1 + X448PublicKeyParameters.KeySize]; + ephPubEncoding[0] = 0x40; + ((X448PublicKeyParameters)ephKp.Public).Encode(ephPubEncoding, 1); - AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); - ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private; - ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public; + return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random); + } + else + { + // Generate the ephemeral key pair + ECDomainParameters ecParams = ((ECPublicKeyParameters)cryptoPublicKey).Parameters; + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.Init(new ECKeyGenerationParameters(ecParams, random)); - ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey(); - ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize(); + AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); - KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S)); + ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.Init(ephKp.Private); + BigInteger S = agreement.CalculateAgreement(cryptoPublicKey); + byte[] secret = BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), S); + + byte[] ephPubEncoding = ((ECPublicKeyParameters)ephKp.Public).Q.GetEncoded(false); + return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random); + } + } + + private byte[] EncryptSessionInfo(ECDHPublicBcpgKey ecPubKey, byte[] sessionInfo, byte[] secret, + byte[] ephPubEncoding, SecureRandom random) + { + var key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, secret)); - IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm); + IWrapper w = PgpUtilities.CreateWrapper(ecPubKey.SymmetricKeyAlgorithm); w.Init(true, new ParametersWithRandom(key, random)); byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo, sessionKeyObfuscation); byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length); - byte[] VB = new MPInteger(MPInteger.ToMpiBigInteger(ephPub.Q)).GetEncoded(); + byte[] VB = new MPInteger(new BigInteger(1, ephPubEncoding)).GetEncoded(); byte[] rv = new byte[VB.Length + 1 + C.Length]; @@ -165,7 +223,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { case PublicKeyAlgorithmTag.RsaEncrypt: case PublicKeyAlgorithmTag.RsaGeneral: - data = new byte[][] { ConvertToEncodedMpi(encryptedSessionInfo) }; + data = new byte[1][] { ConvertToEncodedMpi(encryptedSessionInfo) }; break; case PublicKeyAlgorithmTag.ElGamalEncrypt: case PublicKeyAlgorithmTag.ElGamalGeneral: @@ -176,13 +234,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp Array.Copy(encryptedSessionInfo, 0, b1, 0, halfLength); Array.Copy(encryptedSessionInfo, halfLength, b2, 0, halfLength); - data = new byte[][] { + data = new byte[2][] { ConvertToEncodedMpi(b1), ConvertToEncodedMpi(b2), }; break; case PublicKeyAlgorithmTag.ECDH: - data = new byte[][]{ encryptedSessionInfo }; + data = new byte[1][]{ encryptedSessionInfo }; break; default: throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm); @@ -489,8 +547,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp if (withIntegrityPacket) { - string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1); - IDigest digest = DigestUtilities.GetDigest(digestName); + IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1); myOut = digestOut = new DigestStream(myOut, null, digest); } diff --git a/crypto/src/openpgp/PgpKdfParameters.cs b/crypto/src/openpgp/PgpKdfParameters.cs new file mode 100644 index 000000000..c78448939 --- /dev/null +++ b/crypto/src/openpgp/PgpKdfParameters.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + internal sealed class PgpKdfParameters + //: IPgpAlgorithmParameters + { + private readonly HashAlgorithmTag m_hashAlgorithm; + private readonly SymmetricKeyAlgorithmTag m_symmetricWrapAlgorithm; + + public PgpKdfParameters(HashAlgorithmTag hashAlgorithm, SymmetricKeyAlgorithmTag symmetricWrapAlgorithm) + { + m_hashAlgorithm = hashAlgorithm; + m_symmetricWrapAlgorithm = symmetricWrapAlgorithm; + } + + public HashAlgorithmTag HashAlgorithm => m_hashAlgorithm; + + public SymmetricKeyAlgorithmTag SymmetricWrapAlgorithm => m_symmetricWrapAlgorithm; + } +} diff --git a/crypto/src/openpgp/PgpObjectFactory.cs b/crypto/src/openpgp/PgpObjectFactory.cs index f7bf89507..068b85154 100644 --- a/crypto/src/openpgp/PgpObjectFactory.cs +++ b/crypto/src/openpgp/PgpObjectFactory.cs @@ -72,9 +72,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } case PacketTag.PublicKey: return new PgpPublicKeyRing(bcpgIn); - // TODO Make PgpPublicKey a PgpObject or return a PgpPublicKeyRing - //case PacketTag.PublicSubkey: - // return PgpPublicKeyRing.ReadSubkey(bcpgIn); + case PacketTag.PublicSubkey: + return PgpPublicKeyRing.ReadSubkey(bcpgIn); case PacketTag.CompressedData: return new PgpCompressedData(bcpgIn); case PacketTag.LiteralData: diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs index 2fab5137e..c14e72bf7 100644 --- a/crypto/src/openpgp/PgpOnePassSignature.cs +++ b/crypto/src/openpgp/PgpOnePassSignature.cs @@ -2,7 +2,9 @@ using System; using System.IO; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { @@ -11,10 +13,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { private static OnePassSignaturePacket Cast(Packet packet) { - if (!(packet is OnePassSignaturePacket)) - throw new IOException("unexpected packet in stream: " + packet); + if (packet is OnePassSignaturePacket onePassSignaturePacket) + return onePassSignaturePacket; - return (OnePassSignaturePacket)packet; + throw new IOException("unexpected packet in stream: " + packet); } private readonly OnePassSignaturePacket sigPack; @@ -36,15 +38,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } /// Initialise the signature object for verification. - public void InitVerify( - PgpPublicKey pubKey) + public void InitVerify(PgpPublicKey pubKey) { lastb = 0; + AsymmetricKeyParameter key = pubKey.GetKey(); - try + try { - sig = SignerUtilities.GetSigner( - PgpUtilities.GetSignatureName(sigPack.KeyAlgorithm, sigPack.HashAlgorithm)); + sig = PgpUtilities.CreateSigner(sigPack.KeyAlgorithm, sigPack.HashAlgorithm, key); } catch (Exception e) { @@ -53,7 +54,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp try { - sig.Init(false, pubKey.GetKey()); + sig.Init(false, key); } catch (InvalidKeyException e) { @@ -61,12 +62,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - public void Update( - byte b) + public void Update(byte b) { if (signatureType == PgpSignature.CanonicalTextDocument) { - doCanonicalUpdateByte(b); + DoCanonicalUpdateByte(b); } else { @@ -74,18 +74,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - private void doCanonicalUpdateByte( - byte b) + private void DoCanonicalUpdateByte(byte b) { if (b == '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } else if (b == '\n') { if (lastb != '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } } else @@ -96,51 +95,57 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp lastb = b; } - private void doUpdateCRLF() + private void DoUpdateCRLF() { sig.Update((byte)'\r'); sig.Update((byte)'\n'); } - public void Update( - byte[] bytes) + public void Update(params byte[] bytes) + { + Update(bytes, 0, bytes.Length); + } + + public void Update(byte[] bytes, int off, int length) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Update(bytes.AsSpan(off, length)); +#else if (signatureType == PgpSignature.CanonicalTextDocument) { - for (int i = 0; i != bytes.Length; i++) + int finish = off + length; + + for (int i = off; i != finish; i++) { - doCanonicalUpdateByte(bytes[i]); + DoCanonicalUpdateByte(bytes[i]); } } else { - sig.BlockUpdate(bytes, 0, bytes.Length); + sig.BlockUpdate(bytes, off, length); } +#endif } - public void Update( - byte[] bytes, - int off, - int length) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Update(ReadOnlySpan input) { if (signatureType == PgpSignature.CanonicalTextDocument) { - int finish = off + length; - - for (int i = off; i != finish; i++) + for (int i = 0; i < input.Length; ++i) { - doCanonicalUpdateByte(bytes[i]); + DoCanonicalUpdateByte(input[i]); } } else { - sig.BlockUpdate(bytes, off, length); + sig.BlockUpdate(input); } } +#endif - /// Verify the calculated signature against the passed in PgpSignature. - public bool Verify( - PgpSignature pgpSig) + /// Verify the calculated signature against the passed in PgpSignature. + public bool Verify(PgpSignature pgpSig) { byte[] trailer = pgpSig.GetSignatureTrailer(); @@ -171,15 +176,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public byte[] GetEncoded() { - MemoryStream bOut = new MemoryStream(); + var bOut = new MemoryStream(); Encode(bOut); return bOut.ToArray(); } - public void Encode( - Stream outStr) + public void Encode(Stream outStr) { BcpgOutputStream.Wrap(outStr).WritePacket(sigPack); } diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs index f43f2f512..7920f54ea 100644 --- a/crypto/src/openpgp/PgpPbeEncryptedData.cs +++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs @@ -97,10 +97,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { truncStream = new TruncatedStream(encStream); - string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1); - IDigest digest = DigestUtilities.GetDigest(digestName); + IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1); - encStream = new DigestStream(truncStream, digest, null); + encStream = new DigestStream(truncStream, digest, null); } if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length) diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index cdb8efd36..400cda071 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -3,13 +3,18 @@ using System.Collections.Generic; using System.IO; using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cryptlib; +using Org.BouncyCastle.Asn1.EdEC; using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; 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; using Org.BouncyCastle.Utilities.Collections; @@ -18,7 +23,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { /// General class to handle a PGP public key object. public class PgpPublicKey + : PgpObject { + // We default to these as they are specified as mandatory in RFC 6631. + private static readonly PgpKdfParameters DefaultKdfParameters = new PgpKdfParameters(HashAlgorithmTag.Sha256, + SymmetricKeyAlgorithmTag.Aes128); + public static byte[] CalculateFingerprint(PublicKeyPacket publicPk) { IBcpgKey key = publicPk.Key; @@ -30,7 +40,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp try { - digest = DigestUtilities.GetDigest("MD5"); + digest = PgpUtilities.CreateDigest(HashAlgorithmTag.MD5); + UpdateDigest(digest, rK.Modulus); UpdateDigest(digest, rK.PublicExponent); } @@ -45,7 +56,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { byte[] kBytes = publicPk.GetEncodedContents(); - digest = DigestUtilities.GetDigest("SHA1"); + digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1); digest.Update(0x99); digest.Update((byte)(kBytes.Length >> 8)); @@ -124,27 +135,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength; } - else if (key is ECPublicBcpgKey) + else if (key is EdDsaPublicBcpgKey eddsaK) { - DerObjectIdentifier curveOid = ((ECPublicBcpgKey)key).CurveOid; - if (GnuObjectIdentifiers.Ed25519.Equals(curveOid) - //|| CryptlibObjectIdentifiers.curvey25519.Equals(curveOid) - ) + var curveOid = eddsaK.CurveOid; + if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) || + GnuObjectIdentifiers.Ed25519.Equals(curveOid) || + EdECObjectIdentifiers.id_X25519.Equals(curveOid) || + CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)) { this.keyStrength = 256; } + else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid) || + EdECObjectIdentifiers.id_X448.Equals(curveOid)) + { + this.keyStrength = 448; + } else { - X9ECParametersHolder ecParameters = ECKeyPairGenerator.FindECCurveByOidLazy(curveOid); - - if (ecParameters != null) - { - this.keyStrength = ecParameters.Curve.FieldSize; - } - else - { - this.keyStrength = -1; // unknown - } + this.keyStrength = -1; // unknown + } + } + else if (key is ECPublicBcpgKey ecK) + { + var curveOid = ecK.CurveOid; + X9ECParametersHolder ecParameters = ECKeyPairGenerator.FindECCurveByOidLazy(curveOid); + + if (ecParameters != null) + { + this.keyStrength = ecParameters.Curve.FieldSize; + } + else + { + this.keyStrength = -1; // unknown } } } @@ -165,7 +187,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time) { if (pubKey.IsPrivate) - throw new ArgumentException("Expected a public key", "pubKey"); + throw new ArgumentException("Expected a public key", nameof(pubKey)); IBcpgKey bcpgKey; if (pubKey is RsaKeyParameters rK) @@ -178,6 +200,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y); } + else if (pubKey is ElGamalPublicKeyParameters eK) + { + ElGamalParameters eS = eK.Parameters; + + bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y); + } else if (pubKey is ECPublicKeyParameters ecK) { if (algorithm == PublicKeyAlgorithmTag.ECDH) @@ -194,11 +222,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new PgpException("unknown EC algorithm"); } } - else if (pubKey is ElGamalPublicKeyParameters eK) + else if (pubKey is Ed25519PublicKeyParameters ed25519PubKey) { - ElGamalParameters eS = eK.Parameters; + byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize]; + pointEnc[0] = 0x40; + ed25519PubKey.Encode(pointEnc, 1); + bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc)); + } + else if (pubKey is Ed448PublicKeyParameters ed448PubKey) + { + byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize]; + ed448PubKey.Encode(pointEnc, 0); + bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc)); + } + else if (pubKey is X25519PublicKeyParameters x25519PubKey) + { + byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize]; + pointEnc[0] = 0x40; + x25519PubKey.Encode(pointEnc, 1); - bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y); + PgpKdfParameters kdfParams = DefaultKdfParameters; + + bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc), + kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm); + } + else if (pubKey is X448PublicKeyParameters x448PubKey) + { + byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize]; + x448PubKey.Encode(pointEnc, 0); + + PgpKdfParameters kdfParams = DefaultKdfParameters; + + bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc), + kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm); } else { @@ -473,24 +529,89 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { switch (publicPk.Algorithm) { - case PublicKeyAlgorithmTag.RsaEncrypt: - case PublicKeyAlgorithmTag.RsaGeneral: - case PublicKeyAlgorithmTag.RsaSign: - RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key; - return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent); - case PublicKeyAlgorithmTag.Dsa: - DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key; - return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G)); - case PublicKeyAlgorithmTag.ECDsa: - return GetECKey("ECDSA"); - case PublicKeyAlgorithmTag.ECDH: - return GetECKey("ECDH"); - case PublicKeyAlgorithmTag.ElGamalEncrypt: - case PublicKeyAlgorithmTag.ElGamalGeneral: - ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key; - return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G)); - default: - throw new PgpException("unknown public key algorithm encountered"); + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key; + return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent); + case PublicKeyAlgorithmTag.Dsa: + DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key; + return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G)); + case PublicKeyAlgorithmTag.ECDsa: + ECDsaPublicBcpgKey ecdsaK = (ECDsaPublicBcpgKey)publicPk.Key; + return GetECKey("ECDSA", ecdsaK); + case PublicKeyAlgorithmTag.ECDH: + { + ECDHPublicBcpgKey ecdhK = (ECDHPublicBcpgKey)publicPk.Key; + var curveOid = ecdhK.CurveOid; + + if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) || + CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)) + { + byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + X25519.PointSize, ecdhK.EncodedPoint); + if (pEnc[0] != 0x40) + throw new ArgumentException("Invalid X25519 public key"); + + return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo( + new AlgorithmIdentifier(curveOid), + // TODO Span variant + Arrays.CopyOfRange(pEnc, 1, pEnc.Length))); + } + else if (EdECObjectIdentifiers.id_X448.Equals(curveOid)) + { + byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + X448.PointSize, ecdhK.EncodedPoint); + if (pEnc[0] != 0x40) + throw new ArgumentException("Invalid X448 public key"); + + return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo( + new AlgorithmIdentifier(curveOid), + // TODO Span variant + Arrays.CopyOfRange(pEnc, 1, pEnc.Length))); + } + else + { + return GetECKey("ECDH", ecdhK); + } + } + case PublicKeyAlgorithmTag.EdDsa: + { + EdDsaPublicBcpgKey eddsaK = (EdDsaPublicBcpgKey)publicPk.Key; + var curveOid = eddsaK.CurveOid; + + if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) || + GnuObjectIdentifiers.Ed25519.Equals(curveOid)) + { + byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + Ed25519.PublicKeySize, eddsaK.EncodedPoint); + if (pEnc[0] != 0x40) + throw new ArgumentException("Invalid Ed25519 public key"); + + return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo( + new AlgorithmIdentifier(curveOid), + // TODO Span variant + Arrays.CopyOfRange(pEnc, 1, pEnc.Length))); + } + else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid)) + { + byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + Ed448.PublicKeySize, eddsaK.EncodedPoint); + if (pEnc[0] != 0x40) + throw new ArgumentException("Invalid Ed448 public key"); + + return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo( + new AlgorithmIdentifier(curveOid), + // TODO Span variant + Arrays.CopyOfRange(pEnc, 1, pEnc.Length))); + } + else + { + throw new InvalidOperationException(); + } + } + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key; + return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G)); + default: + throw new PgpException("unknown public key algorithm encountered"); } } catch (PgpException e) @@ -503,9 +624,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - private ECPublicKeyParameters GetECKey(string algorithm) + private ECPublicKeyParameters GetECKey(string algorithm, ECPublicBcpgKey ecK) { - ECPublicBcpgKey ecK = (ECPublicBcpgKey)publicPk.Key; X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(ecK.CurveOid); BigInteger encodedPoint = ecK.EncodedPoint; @@ -563,7 +683,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public IEnumerable GetSignaturesForId(string id) { if (id == null) - throw new ArgumentNullException("id"); + throw new ArgumentNullException(nameof(id)); var result = new List(); bool userIdFound = false; @@ -580,13 +700,48 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return userIdFound ? CollectionUtilities.Proxy(result) : null; } + private IEnumerable GetSignaturesForID(UserIdPacket id) + { + var signatures = new List(); + bool userIdFound = false; + + for (int i = 0; i != ids.Count; i++) + { + if (id.Equals(ids[i])) + { + userIdFound = true; + signatures.AddRange(idSigs[i]); + } + } + + return userIdFound ? signatures : null; + } + + /// Return any signatures associated with the passed in key identifier keyID. + /// the key id to be matched. + /// An IEnumerable of PgpSignature objects issued by the key with keyID. + public IEnumerable GetSignaturesForKeyID(long keyID) + { + var sigs = new List(); + + foreach (var sig in GetSignatures()) + { + if (sig.KeyId == keyID) + { + sigs.Add(sig); + } + } + + return CollectionUtilities.Proxy(sigs); + } + /// Allows enumeration of signatures associated with the passed in user attributes. /// The vector of user attributes to be matched. /// An IEnumerable of PgpSignature objects. public IEnumerable GetSignaturesForUserAttribute(PgpUserAttributeSubpacketVector userAttributes) { if (userAttributes == null) - throw new ArgumentNullException("userAttributes"); + throw new ArgumentNullException(nameof(userAttributes)); var result = new List(); bool attributeFound = false; @@ -648,11 +803,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp */ public IEnumerable GetKeySignatures() { - var result = subSigs; - if (result == null) - { - result = new List(keySigs); - } + var result = subSigs ?? new List(keySigs); return CollectionUtilities.Proxy(result); } @@ -947,56 +1098,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// The key the certifications are to be removed from. /// The certfication to be removed. /// The modified key, null if the certification was not found. - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - PgpSignature certification) + public static PgpPublicKey RemoveCertification(PgpPublicKey key, PgpSignature certification) { - PgpPublicKey returnKey = new PgpPublicKey(key); - var sigs = returnKey.subSigs != null - ? returnKey.subSigs - : returnKey.keySigs; + var returnKey = new PgpPublicKey(key); + var sigs = returnKey.subSigs ?? returnKey.keySigs; -// bool found = sigs.Remove(certification); - int pos = sigs.IndexOf(certification); - bool found = pos >= 0; + if (sigs.Remove(certification)) + return returnKey; - if (found) + // TODO Java uses getRawUserIDs + foreach (string id in key.GetUserIds()) { - sigs.RemoveAt(pos); + if (ContainsSignature(key.GetSignaturesForId(id), certification)) + return RemoveCertification(returnKey, id, certification); } - else - { - foreach (string id in key.GetUserIds()) - { - foreach (object sig in key.GetSignaturesForId(id)) - { - // TODO Is this the right type of equality test? - if (certification == sig) - { - found = true; - returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); - } - } - } - if (!found) - { - foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) - { - foreach (object sig in key.GetSignaturesForUserAttribute(id)) - { - // TODO Is this the right type of equality test? - if (certification == sig) - { - found = true; - returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); - } - } - } - } + foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) + { + if (ContainsSignature(key.GetSignaturesForUserAttribute(id), certification)) + return RemoveCertification(returnKey, id, certification); } return returnKey; } + + private static bool ContainsSignature(IEnumerable signatures, PgpSignature signature) + { + foreach (PgpSignature candidate in signatures) + { + if (signature == candidate) + return true; + } + return false; + } } } diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs index 04fe3ad37..8c6fcda53 100644 --- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs +++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs @@ -1,19 +1,20 @@ using System; using System.IO; -using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Asn1.Cryptlib; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.IO; -using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Asn1.EdEC; namespace Org.BouncyCastle.Bcpg.OpenPgp { - /// A public key encrypted data object. + /// A public key encrypted data object. public class PgpPublicKeyEncryptedData : PgpEncryptedData { @@ -139,10 +140,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { truncStream = new TruncatedStream(encStream); - string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1); - IDigest digest = DigestUtilities.GetDigest(digestName); + IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1); - encStream = new DigestStream(truncStream, digest, null); + encStream = new DigestStream(truncStream, digest, null); } if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length) @@ -189,76 +189,114 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { byte[][] secKeyData = keyData.GetEncSessionKey(); - if (keyData.Algorithm == PublicKeyAlgorithmTag.ECDH) + if (keyData.Algorithm != PublicKeyAlgorithmTag.ECDH) { - ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key; - X9ECParameters x9Params = ECKeyPairGenerator.FindECCurveByOid(ecKey.CurveOid); + IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm); + + try + { + cipher.Init(false, privKey.Key); + } + catch (InvalidKeyException e) + { + throw new PgpException("error setting asymmetric cipher", e); + } + + if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt + || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral) + { + byte[] bi = secKeyData[0]; + + cipher.ProcessBytes(bi, 2, bi.Length - 2); + } + else + { + ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key; + int size = (k.Parameters.P.BitLength + 7) / 8; + + ProcessEncodedMpi(cipher, size, secKeyData[0]); + ProcessEncodedMpi(cipher, size, secKeyData[1]); + } + + try + { + return cipher.DoFinal(); + } + catch (Exception e) + { + throw new PgpException("exception decrypting secret key", e); + } + } - byte[] enc = secKeyData[0]; + ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key; + byte[] enc = secKeyData[0]; - int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; - if ((2 + pLen + 1) > enc.Length) - throw new PgpException("encoded length out of range"); + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + if ((2 + pLen + 1) > enc.Length) + throw new PgpException("encoded length out of range"); - byte[] pEnc = new byte[pLen]; - Array.Copy(enc, 2, pEnc, 0, pLen); + byte[] pEnc = new byte[pLen]; + Array.Copy(enc, 2, pEnc, 0, pLen); - int keyLen = enc[pLen + 2]; - if ((2 + pLen + 1 + keyLen) > enc.Length) - throw new PgpException("encoded length out of range"); + int keyLen = enc[pLen + 2]; + if ((2 + pLen + 1 + keyLen) > enc.Length) + throw new PgpException("encoded length out of range"); - byte[] keyEnc = new byte[keyLen]; - Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length); + byte[] keyEnc = new byte[keyLen]; + Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length); - ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc); + var curveOid = ecPubKey.CurveOid; + byte[] secret; - ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key; - ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize(); + if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) || + CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)) + { + // skip the 0x40 header byte. + if (pEnc.Length != (1 + X25519PublicKeyParameters.KeySize) || 0x40 != pEnc[0]) + throw new ArgumentException("Invalid X25519 public key"); - KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S)); + X25519PublicKeyParameters ephPub = new X25519PublicKeyParameters(pEnc, 1); - IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm); - w.Init(false, key); + X25519Agreement agreement = new X25519Agreement(); + agreement.Init(privKey.Key); - return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length)); + secret = new byte[agreement.AgreementSize]; + agreement.CalculateAgreement(ephPub, secret, 0); } + else if (EdECObjectIdentifiers.id_X448.Equals(curveOid)) + { + // skip the 0x40 header byte. + if (pEnc.Length != (1 + X448PublicKeyParameters.KeySize) || 0x40 != pEnc[0]) + throw new ArgumentException("Invalid X448 public key"); - IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm); + X448PublicKeyParameters ephPub = new X448PublicKeyParameters(pEnc, 1); - try - { - cipher.Init(false, privKey.Key); - } - catch (InvalidKeyException e) - { - throw new PgpException("error setting asymmetric cipher", e); - } + X448Agreement agreement = new X448Agreement(); + agreement.Init(privKey.Key); - if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt - || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral) - { - byte[] bi = secKeyData[0]; + secret = new byte[agreement.AgreementSize]; + agreement.CalculateAgreement(ephPub, secret, 0); + } + else + { + ECDomainParameters ecParameters = ((ECPrivateKeyParameters)privKey.Key).Parameters; - cipher.ProcessBytes(bi, 2, bi.Length - 2); - } - else - { - ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key; - int size = (k.Parameters.P.BitLength + 7) / 8; + ECPublicKeyParameters ephPub = new ECPublicKeyParameters(ecParameters.Curve.DecodePoint(pEnc), + ecParameters); - ProcessEncodedMpi(cipher, size, secKeyData[0]); - ProcessEncodedMpi(cipher, size, secKeyData[1]); - } + ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.Init(privKey.Key); + BigInteger S = agreement.CalculateAgreement(ephPub); + secret = BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), S); + } - try - { - return cipher.DoFinal(); - } - catch (Exception e) - { - throw new PgpException("exception decrypting secret key", e); - } - } + KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, secret)); + + IWrapper w = PgpUtilities.CreateWrapper(ecPubKey.SymmetricKeyAlgorithm); + w.Init(false, key); + + return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length)); + } private static void ProcessEncodedMpi(IBufferedCipher cipher, int size, byte[] mpiEnc) { diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs index 4aa15384c..ebbb95634 100644 --- a/crypto/src/openpgp/PgpPublicKeyRing.cs +++ b/crypto/src/openpgp/PgpPublicKeyRing.cs @@ -68,19 +68,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// Return the first public key in the ring. public virtual PgpPublicKey GetPublicKey() { - return (PgpPublicKey) keys[0]; + return keys[0]; } /// Return the public key referred to by the passed in key ID if it is present. - public virtual PgpPublicKey GetPublicKey( - long keyId) + public virtual PgpPublicKey GetPublicKey(long keyId) { foreach (PgpPublicKey k in keys) { if (keyId == k.KeyId) - { return k; - } } return null; @@ -168,23 +165,24 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// A new PgpPublicKeyRing, or null if pubKey is not found. public static PgpPublicKeyRing RemovePublicKey(PgpPublicKeyRing pubRing, PgpPublicKey pubKey) { - var keys = new List(pubRing.keys); + int count = pubRing.keys.Count; + long keyID = pubKey.KeyId; + + var result = new List(count); bool found = false; - // TODO Is there supposed to be at most a single match? - int pos = keys.Count; - while (--pos >= 0) + foreach (var key in pubRing.keys) { - PgpPublicKey key = keys[pos]; - - if (key.KeyId == pubKey.KeyId) + if (key.KeyId == keyID) { found = true; - keys.RemoveAt(pos); + continue; } + + result.Add(key); } - return found ? new PgpPublicKeyRing(keys) : null; + return found ? new PgpPublicKeyRing(result) : null; } internal static PublicKeyPacket ReadPublicKeyPacket(BcpgInputStream bcpgInput) diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 51a45703a..0103cc187 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -2,10 +2,17 @@ using System; using System.Collections.Generic; using System.IO; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cryptlib; +using Org.BouncyCastle.Asn1.EdEC; +using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Rfc8032; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; @@ -13,6 +20,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { /// General class to handle a PGP secret key object. public class PgpSecretKey + : PgpObject { private readonly SecretKeyPacket secret; private readonly PgpPublicKey pub; @@ -52,10 +60,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp secKey = new DsaSecretBcpgKey(dsK.X); break; case PublicKeyAlgorithmTag.ECDH: + { + if (privKey.Key is ECPrivateKeyParameters ecdhK) + { + secKey = new ECSecretBcpgKey(ecdhK.D); + } + else + { + // 'reverse' because the native format for X25519 private keys is little-endian + X25519PrivateKeyParameters xK = (X25519PrivateKeyParameters)privKey.Key; + secKey = new ECSecretBcpgKey(new BigInteger(1, Arrays.ReverseInPlace(xK.GetEncoded()))); + } + break; + } case PublicKeyAlgorithmTag.ECDsa: ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key; secKey = new ECSecretBcpgKey(ecK.D); break; + case PublicKeyAlgorithmTag.EdDsa: + { + if (privKey.Key is Ed25519PrivateKeyParameters ed25519K) + { + secKey = new EdSecretBcpgKey(new BigInteger(1, ed25519K.GetEncoded())); + } + else if (privKey.Key is Ed448PrivateKeyParameters ed448K) + { + secKey = new EdSecretBcpgKey(new BigInteger(1, ed448K.GetEncoded())); + } + else + { + throw new PgpException("unknown EdDSA key class"); + } + break; + } case PublicKeyAlgorithmTag.ElGamalEncrypt: case PublicKeyAlgorithmTag.ElGamalGeneral: ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key; @@ -625,6 +662,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return null; PublicKeyPacket pubPk = secret.PublicKeyPacket; + try { byte[] data = ExtractKeyData(rawPassPhrase, clearPassPhrase); @@ -655,11 +693,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams); break; case PublicKeyAlgorithmTag.ECDH: - privateKey = GetECKey("ECDH", bcpgIn); + { + ECDHPublicBcpgKey ecdhPub = (ECDHPublicBcpgKey)pubPk.Key; + ECSecretBcpgKey ecdhPriv = new ECSecretBcpgKey(bcpgIn); + var curveOid = ecdhPub.CurveOid; + + if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) || + CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)) + { + // 'reverse' because the native format for X25519 private keys is little-endian + privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo( + new AlgorithmIdentifier(curveOid), + new DerOctetString(Arrays.ReverseInPlace(BigIntegers.AsUnsignedByteArray(ecdhPriv.X))))); + } + else if (EdECObjectIdentifiers.id_X448.Equals(curveOid)) + { + // 'reverse' because the native format for X448 private keys is little-endian + privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo( + new AlgorithmIdentifier(curveOid), + new DerOctetString(Arrays.ReverseInPlace(BigIntegers.AsUnsignedByteArray(ecdhPriv.X))))); + } + else + { + privateKey = new ECPrivateKeyParameters("ECDH", ecdhPriv.X, ecdhPub.CurveOid); + } break; + } case PublicKeyAlgorithmTag.ECDsa: - privateKey = GetECKey("ECDSA", bcpgIn); + { + ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)pubPk.Key; + ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn); + + privateKey = new ECPrivateKeyParameters("ECDSA", ecdsaPriv.X, ecdsaPub.CurveOid); break; + } + case PublicKeyAlgorithmTag.EdDsa: + { + EdDsaPublicBcpgKey eddsaPub = (EdDsaPublicBcpgKey)pubPk.Key; + EdSecretBcpgKey ecdsaPriv = new EdSecretBcpgKey(bcpgIn); + + var curveOid = eddsaPub.CurveOid; + if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) || + GnuObjectIdentifiers.Ed25519.Equals(curveOid)) + { + privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo( + new AlgorithmIdentifier(curveOid), + new DerOctetString(BigIntegers.AsUnsignedByteArray(Ed25519.SecretKeySize, ecdsaPriv.X)))); + } + else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid)) + { + privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo( + new AlgorithmIdentifier(curveOid), + new DerOctetString(BigIntegers.AsUnsignedByteArray(Ed448.SecretKeySize, ecdsaPriv.X)))); + } + else + { + throw new InvalidOperationException(); + } + break; + } case PublicKeyAlgorithmTag.ElGamalEncrypt: case PublicKeyAlgorithmTag.ElGamalGeneral: ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key; @@ -683,13 +775,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - private ECPrivateKeyParameters GetECKey(string algorithm, BcpgInputStream bcpgIn) - { - ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)secret.PublicKeyPacket.Key; - ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn); - return new ECPrivateKeyParameters(algorithm, ecdsaPriv.X, ecdsaPub.CurveOid); - } - private static byte[] Checksum( bool useSha1, byte[] bytes, @@ -699,7 +784,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { try { - IDigest dig = DigestUtilities.GetDigest("SHA1"); + IDigest dig = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1); dig.BlockUpdate(bytes, 0, length); return DigestUtilities.DoFinal(dig); } diff --git a/crypto/src/openpgp/PgpSecretKeyRing.cs b/crypto/src/openpgp/PgpSecretKeyRing.cs index 637cb45f8..a070aa132 100644 --- a/crypto/src/openpgp/PgpSecretKeyRing.cs +++ b/crypto/src/openpgp/PgpSecretKeyRing.cs @@ -71,11 +71,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // revocation and direct signatures var keySigs = ReadSignaturesAndTrust(bcpgInput); - IList ids; - IList idTrusts; - IList> idSigs; - - ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); + ReadUserIDs(bcpgInput, out var ids, out var idTrusts, out var idSigs); keys.Add(new PgpSecretKey(secret, new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs))); @@ -119,6 +115,43 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return keys[0].PublicKey; } + /** + * Return any keys carrying a signature issued by the key represented by keyID. + * + * @param keyID the key id to be matched against. + * @return an iterator (possibly empty) of PGPPublicKey objects carrying signatures from keyID. + */ + public IEnumerable GetKeysWithSignaturesBy(long keyID) + { + var keysWithSigs = new List(); + + foreach (var k in GetPublicKeys()) + { + var sigIt = k.GetSignaturesForKeyID(keyID).GetEnumerator(); + + if (sigIt.MoveNext()) + { + keysWithSigs.Add(k); + } + } + + return CollectionUtilities.Proxy(keysWithSigs); + } + + public IEnumerable GetPublicKeys() + { + var pubKeys = new List(); + + foreach (var secretKey in keys) + { + pubKeys.Add(secretKey.PublicKey); + } + + pubKeys.AddRange(extraPubKeys); + + return CollectionUtilities.Proxy(pubKeys); + } + /// Return the master private key. public PgpSecretKey GetSecretKey() { @@ -156,7 +189,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public byte[] GetEncoded() { - MemoryStream bOut = new MemoryStream(); + var bOut = new MemoryStream(); Encode(bOut); return bOut.ToArray(); } @@ -165,7 +198,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp Stream outStr) { if (outStr == null) - throw new ArgumentNullException("outStr"); + throw new ArgumentNullException(nameof(outStr)); foreach (PgpSecretKey key in keys) { diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs index da00d43eb..9b596f279 100644 --- a/crypto/src/openpgp/PgpSignature.cs +++ b/crypto/src/openpgp/PgpSignature.cs @@ -3,6 +3,8 @@ using System.IO; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Rfc8032; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Date; @@ -14,10 +16,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { private static SignaturePacket Cast(Packet packet) { - if (!(packet is SignaturePacket)) - throw new IOException("unexpected packet in stream: " + packet); + if (packet is SignaturePacket signaturePacket) + return signaturePacket; - return (SignaturePacket)packet; + throw new IOException("unexpected packet in stream: " + packet); } public const int BinaryDocument = 0x00; @@ -56,24 +58,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { } - internal PgpSignature( - SignaturePacket sigPacket, - TrustPacket trustPacket) + internal PgpSignature(SignaturePacket sigPacket, TrustPacket trustPacket) { - if (sigPacket == null) - throw new ArgumentNullException("sigPacket"); - - this.sigPck = sigPacket; + this.sigPck = sigPacket ?? throw new ArgumentNullException(nameof(sigPacket)); this.signatureType = sigPck.SignatureType; this.trustPck = trustPacket; } - private void GetSig() - { - this.sig = SignerUtilities.GetSigner( - PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm)); - } - /// The OpenPGP version number for this signature. public int Version { @@ -98,17 +89,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return IsCertification(SignatureType); } - public void InitVerify( - PgpPublicKey pubKey) + public void InitVerify(PgpPublicKey pubKey) { lastb = 0; + AsymmetricKeyParameter key = pubKey.GetKey(); + if (sig == null) - { - GetSig(); + { + this.sig = PgpUtilities.CreateSigner(sigPck.KeyAlgorithm, sigPck.HashAlgorithm, key); } + try { - sig.Init(false, pubKey.GetKey()); + sig.Init(false, key); } catch (InvalidKeyException e) { @@ -116,12 +109,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - public void Update( - byte b) + public void Update(byte b) { if (signatureType == CanonicalTextDocument) { - doCanonicalUpdateByte(b); + DoCanonicalUpdateByte(b); } else { @@ -129,18 +121,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - private void doCanonicalUpdateByte( - byte b) + private void DoCanonicalUpdateByte(byte b) { if (b == '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } else if (b == '\n') { if (lastb != '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } } else @@ -151,39 +142,56 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp lastb = b; } - private void doUpdateCRLF() + private void DoUpdateCRLF() { sig.Update((byte)'\r'); sig.Update((byte)'\n'); } - public void Update( - params byte[] bytes) + public void Update(params byte[] bytes) { Update(bytes, 0, bytes.Length); } - public void Update( - byte[] bytes, - int off, - int length) + public void Update(byte[] bytes, int off, int length) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Update(bytes.AsSpan(off, length)); +#else if (signatureType == CanonicalTextDocument) { int finish = off + length; for (int i = off; i != finish; i++) { - doCanonicalUpdateByte(bytes[i]); + DoCanonicalUpdateByte(bytes[i]); } } else { sig.BlockUpdate(bytes, off, length); } +#endif } - public bool Verify() +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Update(ReadOnlySpan input) + { + if (signatureType == CanonicalTextDocument) + { + for (int i = 0; i < input.Length; ++i) + { + DoCanonicalUpdateByte(input[i]); + } + } + else + { + sig.BlockUpdate(input); + } + } +#endif + + public bool Verify() { byte[] trailer = GetSignatureTrailer(); sig.BlockUpdate(trailer, 0, trailer.Length); @@ -234,7 +242,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // try { - MemoryStream bOut = new MemoryStream(); + var bOut = new MemoryStream(); foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray()) { packet.Encode(bOut); @@ -248,7 +256,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.Update(sigPck.GetSignatureTrailer()); - return sig.VerifySignature(this.GetSignature()); + return sig.VerifySignature(GetSignature()); } /// @@ -345,15 +353,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public PgpSignatureSubpacketVector GetHashedSubPackets() { - return createSubpacketVector(sigPck.GetHashedSubPackets()); + return CreateSubpacketVector(sigPck.GetHashedSubPackets()); } public PgpSignatureSubpacketVector GetUnhashedSubPackets() { - return createSubpacketVector(sigPck.GetUnhashedSubPackets()); + return CreateSubpacketVector(sigPck.GetUnhashedSubPackets()); } - private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks) + private static PgpSignatureSubpacketVector CreateSubpacketVector(SignatureSubpacket[] pcks) { return pcks == null ? null : new PgpSignatureSubpacketVector(pcks); } @@ -369,10 +377,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { signature = sigValues[0].Value.ToByteArrayUnsigned(); } - else - { - try + else if (KeyAlgorithm == PublicKeyAlgorithmTag.EdDsa) + { + if (sigValues.Length != 2) + throw new InvalidOperationException(); + + BigInteger v0 = sigValues[0].Value; + BigInteger v1 = sigValues[1].Value; + + if (v0.BitLength == 918 && + v1.Equals(BigInteger.Zero) && + v0.ShiftRight(912).Equals(BigInteger.ValueOf(0x40))) + { + signature = new byte[Ed448.SignatureSize]; + BigIntegers.AsUnsignedByteArray(v0.ClearBit(918), signature, 0, signature.Length); + } + else if (v0.BitLength <= 256 && v1.BitLength <= 256) + { + signature = new byte[Ed25519.SignatureSize]; + BigIntegers.AsUnsignedByteArray(sigValues[0].Value, signature, 0, 32); + BigIntegers.AsUnsignedByteArray(sigValues[1].Value, signature, 32, 32); + } + else { + throw new InvalidOperationException(); + } + } + else + { + if (sigValues.Length != 2) + throw new InvalidOperationException(); + + try + { signature = new DerSequence( new DerInteger(sigValues[0].Value), new DerInteger(sigValues[1].Value)).GetEncoded(); @@ -394,17 +431,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // TODO Handle the encoding stuff by subclassing BcpgObject? public byte[] GetEncoded() { - MemoryStream bOut = new MemoryStream(); + var bOut = new MemoryStream(); Encode(bOut); return bOut.ToArray(); } - public void Encode( - Stream outStream) + public void Encode(Stream outStream) { - BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream); + var bcpgOut = BcpgOutputStream.Wrap(outStream); bcpgOut.WritePacket(sigPck); @@ -414,8 +450,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - private byte[] GetEncodedPublicKey( - PgpPublicKey pubKey) + private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey) { try { @@ -436,13 +471,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { switch (signatureType) { - case DefaultCertification: - case NoCertification: - case CasualCertification: - case PositiveCertification: - return true; - default: - return false; + case DefaultCertification: + case NoCertification: + case CasualCertification: + case PositiveCertification: + return true; + default: + return false; } } } diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs index c5309689f..12edf9f89 100644 --- a/crypto/src/openpgp/PgpSignatureGenerator.cs +++ b/crypto/src/openpgp/PgpSignatureGenerator.cs @@ -5,19 +5,20 @@ using Org.BouncyCastle.Bcpg.Sig; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Rfc8032; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { /// Generator for PGP signatures. - // TODO Should be able to implement ISigner? public class PgpSignatureGenerator { private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0]; - private PublicKeyAlgorithmTag keyAlgorithm; - private HashAlgorithmTag hashAlgorithm; + private readonly PublicKeyAlgorithmTag keyAlgorithm; + private readonly HashAlgorithmTag hashAlgorithm; + private PgpPrivateKey privKey; private ISigner sig; private IDigest dig; @@ -35,33 +36,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.keyAlgorithm = keyAlgorithm; this.hashAlgorithm = hashAlgorithm; - dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm)); - sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm)); + dig = PgpUtilities.CreateDigest(hashAlgorithm); } /// Initialise the generator for signing. public void InitSign( int sigType, - PgpPrivateKey key) + PgpPrivateKey privKey) { - InitSign(sigType, key, null); + InitSign(sigType, privKey, null); } /// Initialise the generator for signing. public void InitSign( int sigType, - PgpPrivateKey key, + PgpPrivateKey privKey, SecureRandom random) { - this.privKey = key; + this.privKey = privKey; this.signatureType = sigType; - try + AsymmetricKeyParameter key = privKey.Key; + + if (sig == null) { - ICipherParameters cp = key.Key; + this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key); + } + + try + { + ICipherParameters cp = key; if (random != null) { - cp = new ParametersWithRandom(key.Key, random); + cp = new ParametersWithRandom(cp, random); } sig.Init(true, cp); @@ -75,72 +82,68 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp lastb = 0; } - public void Update( - byte b) + public void Update(byte b) { if (signatureType == PgpSignature.CanonicalTextDocument) { - doCanonicalUpdateByte(b); + DoCanonicalUpdateByte(b); } else { - doUpdateByte(b); + DoUpdateByte(b); } } - private void doCanonicalUpdateByte( - byte b) + private void DoCanonicalUpdateByte(byte b) { if (b == '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } else if (b == '\n') { if (lastb != '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } } else { - doUpdateByte(b); + DoUpdateByte(b); } lastb = b; } - private void doUpdateCRLF() + private void DoUpdateCRLF() { - doUpdateByte((byte)'\r'); - doUpdateByte((byte)'\n'); + DoUpdateByte((byte)'\r'); + DoUpdateByte((byte)'\n'); } - private void doUpdateByte( - byte b) + private void DoUpdateByte(byte b) { sig.Update(b); dig.Update(b); } - public void Update( - params byte[] b) + public void Update(params byte[] b) { Update(b, 0, b.Length); } - public void Update( - byte[] b, - int off, - int len) + public void Update(byte[] b, int off, int len) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Update(b.AsSpan(off, len)); +#else if (signatureType == PgpSignature.CanonicalTextDocument) { int finish = off + len; for (int i = off; i != finish; i++) { - doCanonicalUpdateByte(b[i]); + DoCanonicalUpdateByte(b[i]); } } else @@ -148,9 +151,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp sig.BlockUpdate(b, off, len); dig.BlockUpdate(b, off, len); } +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Update(ReadOnlySpan input) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + for (int i = 0; i < input.Length; ++i) + { + DoCanonicalUpdateByte(input[i]); + } + } + else + { + sig.BlockUpdate(input); + dig.BlockUpdate(input); + } } +#endif - public void SetHashedSubpackets( + public void SetHashedSubpackets( PgpSignatureSubpacketVector hashedPackets) { hashed = hashedPackets == null @@ -180,15 +202,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed; - if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime)) + if (!IsPacketPresent(hashed, SignatureSubpacketTag.CreationTime)) { - hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow)); + hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow)); } - if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId) - && !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId)) + if (!IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId) + && !IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId)) { - unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId)); + unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId)); } int version = 4; @@ -239,17 +261,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp byte[] sigBytes = sig.GenerateSignature(); byte[] digest = DigestUtilities.DoFinal(dig); - byte[] fingerPrint = new byte[] { digest[0], digest[1] }; + byte[] fingerPrint = new byte[2]{ digest[0], digest[1] }; - // an RSA signature - bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign - || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral; - - MPInteger[] sigValues = isRsa - ? PgpUtilities.RsaSigToMpi(sigBytes) - : PgpUtilities.DsaSigToMpi(sigBytes); + MPInteger[] sigValues; + if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa) + { + int sigLen = sigBytes.Length; + if (sigLen == Ed25519.SignatureSize) + { + sigValues = new MPInteger[2]{ + new MPInteger(new BigInteger(1, sigBytes, 0, 32)), + new MPInteger(new BigInteger(1, sigBytes, 32, 32)) + }; + } + else if (sigLen == Ed448.SignatureSize) + { + sigValues = new MPInteger[2]{ + new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))), + new MPInteger(BigInteger.Zero) + }; + } + else + { + throw new InvalidOperationException(); + } + } + else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral) + { + sigValues = PgpUtilities.RsaSigToMpi(sigBytes); + } + else + { + sigValues = PgpUtilities.DsaSigToMpi(sigBytes); + } - return new PgpSignature( + return new PgpSignature( new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm, hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues)); } @@ -258,9 +304,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// The ID we are certifying against the public key. /// The key we are certifying against the ID. /// The certification. - public PgpSignature GenerateCertification( - string id, - PgpPublicKey pubKey) + public PgpSignature GenerateCertification(string id, PgpPublicKey pubKey) { UpdateWithPublicKey(pubKey); @@ -276,9 +320,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// The ID we are certifying against the public key. /// The key we are certifying against the ID. /// The certification. - public PgpSignature GenerateCertification( - PgpUserAttributeSubpacketVector userAttributes, - PgpPublicKey pubKey) + public PgpSignature GenerateCertification(PgpUserAttributeSubpacketVector userAttributes, PgpPublicKey pubKey) { UpdateWithPublicKey(pubKey); @@ -287,7 +329,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // try { - MemoryStream bOut = new MemoryStream(); + var bOut = new MemoryStream(); foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray()) { packet.Encode(bOut); @@ -299,16 +341,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new PgpException("cannot encode subpacket array", e); } - return this.Generate(); + return Generate(); } /// Generate a certification for the passed in key against the passed in master key. /// The key we are certifying against. /// The key we are certifying. /// The certification. - public PgpSignature GenerateCertification( - PgpPublicKey masterKey, - PgpPublicKey pubKey) + public PgpSignature GenerateCertification(PgpPublicKey masterKey, PgpPublicKey pubKey) { UpdateWithPublicKey(masterKey); UpdateWithPublicKey(pubKey); @@ -319,16 +359,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// Generate a certification, such as a revocation, for the passed in key. /// The key we are certifying. /// The certification. - public PgpSignature GenerateCertification( - PgpPublicKey pubKey) + public PgpSignature GenerateCertification(PgpPublicKey pubKey) { UpdateWithPublicKey(pubKey); return Generate(); } - private byte[] GetEncodedPublicKey( - PgpPublicKey pubKey) + private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey) { try { @@ -340,42 +378,31 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - private bool packetPresent( - SignatureSubpacket[] packets, - SignatureSubpacketTag type) + private static bool IsPacketPresent(SignatureSubpacket[] packets, SignatureSubpacketTag type) { for (int i = 0; i != packets.Length; i++) { if (packets[i].SubpacketType == type) - { return true; - } } return false; } - private SignatureSubpacket[] insertSubpacket( - SignatureSubpacket[] packets, - SignatureSubpacket subpacket) + private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket) { - SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1]; - tmp[0] = subpacket; - packets.CopyTo(tmp, 1); - return tmp; + return Arrays.Prepend(packets, subpacket); } - private void UpdateWithIdData( - int header, - byte[] idBytes) + private void UpdateWithIdData(int header, byte[] idBytes) { - this.Update( + Update( (byte) header, (byte)(idBytes.Length >> 24), (byte)(idBytes.Length >> 16), (byte)(idBytes.Length >> 8), (byte)(idBytes.Length)); - this.Update(idBytes); + Update(idBytes); } private void UpdateWithPublicKey( @@ -383,11 +410,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { byte[] keyBytes = GetEncodedPublicKey(key); - this.Update( - (byte) 0x99, + Update( + 0x99, (byte)(keyBytes.Length >> 8), (byte)(keyBytes.Length)); - this.Update(keyBytes); + Update(keyBytes); } } } diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs index f33969ea8..2642f3497 100644 --- a/crypto/src/openpgp/PgpUtilities.cs +++ b/crypto/src/openpgp/PgpUtilities.cs @@ -9,7 +9,9 @@ using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pqc.Crypto.SphincsPlus; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; @@ -114,7 +116,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp if (NameToHashID.TryGetValue(name, out var hashAlgorithmTag)) return (int)hashAlgorithmTag; - throw new ArgumentException("unable to map " + name + " to a hash id", "name"); + throw new ArgumentException("unable to map " + name + " to a hash id", nameof(name)); } /** @@ -152,6 +154,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp case PublicKeyAlgorithmTag.ECDsa: encAlg = "ECDSA"; break; + case PublicKeyAlgorithmTag.EdDsa: + encAlg = "EdDSA"; + break; case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases. case PublicKeyAlgorithmTag.ElGamalGeneral: encAlg = "ElGamal"; @@ -163,7 +168,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return GetDigestName(hashAlgorithm) + "with" + encAlg; } - public static string GetSymmetricCipherName( + public static string GetSymmetricCipherName( SymmetricKeyAlgorithmTag algorithm) { switch (algorithm) @@ -301,11 +306,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp IDigest digest; if (s2k != null) { - string digestName = GetDigestName(s2k.HashAlgorithm); - try { - digest = DigestUtilities.GetDigest(digestName); + digest = CreateDigest(s2k.HashAlgorithm); } catch (Exception e) { @@ -368,7 +371,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { try { - digest = DigestUtilities.GetDigest("MD5"); + digest = CreateDigest(HashAlgorithmTag.MD5); for (int i = 0; i != loopCount; i++) { @@ -407,7 +410,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return MakeKey(algorithm, keyBytes); } -#if !PORTABLE || DOTNET /// Write out the passed in file as a literal data packet. public static void WriteFileToLiteralData( Stream output, @@ -452,12 +454,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp Platform.Dispose(inputStream); } } -#endif private const int ReadAhead = 60; - private static bool IsPossiblyBase64( - int ch) + private static bool IsPossiblyBase64(int ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/') @@ -473,7 +473,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { // TODO Remove this restriction? if (!inputStream.CanSeek) - throw new ArgumentException("inputStream must be seek-able", "inputStream"); + throw new ArgumentException("inputStream must be seek-able", nameof(inputStream)); long markedPos = inputStream.Position; @@ -552,6 +552,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } + internal static IDigest CreateDigest(HashAlgorithmTag hashAlgorithm) + { + return DigestUtilities.GetDigest(GetDigestName(hashAlgorithm)); + } + + internal static ISigner CreateSigner(PublicKeyAlgorithmTag publicKeyAlgorithm, HashAlgorithmTag hashAlgorithm, + AsymmetricKeyParameter key) + { + switch (publicKeyAlgorithm) + { + case PublicKeyAlgorithmTag.EdDsa: + { + ISigner signer; + if (key is Ed25519PrivateKeyParameters || key is Ed25519PublicKeyParameters) + { + signer = new Ed25519Signer(); + } + else if (key is Ed448PrivateKeyParameters || key is Ed448PublicKeyParameters) + { + signer = new Ed448Signer(Arrays.EmptyBytes); + } + else + { + throw new InvalidOperationException(); + } + + return new EdDsaSigner(signer, CreateDigest(hashAlgorithm)); + } + default: + { + return SignerUtilities.GetSigner(GetSignatureName(publicKeyAlgorithm, hashAlgorithm)); + } + } + } + internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm) { switch (encAlgorithm) diff --git a/crypto/src/openpgp/PgpV3SignatureGenerator.cs b/crypto/src/openpgp/PgpV3SignatureGenerator.cs index c7113e0ae..324dbd768 100644 --- a/crypto/src/openpgp/PgpV3SignatureGenerator.cs +++ b/crypto/src/openpgp/PgpV3SignatureGenerator.cs @@ -1,3 +1,5 @@ +using System; + using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; @@ -6,11 +8,11 @@ using Org.BouncyCastle.Utilities.Date; namespace Org.BouncyCastle.Bcpg.OpenPgp { /// Generator for old style PGP V3 Signatures. - // TODO Should be able to implement ISigner? public class PgpV3SignatureGenerator { - private PublicKeyAlgorithmTag keyAlgorithm; - private HashAlgorithmTag hashAlgorithm; + private readonly PublicKeyAlgorithmTag keyAlgorithm; + private readonly HashAlgorithmTag hashAlgorithm; + private PgpPrivateKey privKey; private ISigner sig; private IDigest dig; @@ -22,36 +24,40 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PublicKeyAlgorithmTag keyAlgorithm, HashAlgorithmTag hashAlgorithm) { + if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa) + throw new ArgumentException("Invalid algorithm for V3 signature", nameof(keyAlgorithm)); + this.keyAlgorithm = keyAlgorithm; this.hashAlgorithm = hashAlgorithm; - dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm)); - sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm)); + dig = PgpUtilities.CreateDigest(hashAlgorithm); } /// Initialise the generator for signing. - public void InitSign( - int sigType, - PgpPrivateKey key) + public void InitSign(int sigType, PgpPrivateKey privKey) { - InitSign(sigType, key, null); + InitSign(sigType, privKey, null); } /// Initialise the generator for signing. - public void InitSign( - int sigType, - PgpPrivateKey key, - SecureRandom random) + public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random) { - this.privKey = key; + this.privKey = privKey; this.signatureType = sigType; - try + AsymmetricKeyParameter key = privKey.Key; + + if (sig == null) { - ICipherParameters cp = key.Key; + this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key); + } + + try + { + ICipherParameters cp = key; if (random != null) { - cp = new ParametersWithRandom(key.Key, random); + cp = new ParametersWithRandom(cp, random); } sig.Init(true, cp); @@ -65,93 +71,98 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp lastb = 0; } - public void Update( - byte b) + public void Update(byte b) { if (signatureType == PgpSignature.CanonicalTextDocument) { - doCanonicalUpdateByte(b); + DoCanonicalUpdateByte(b); } else { - doUpdateByte(b); + DoUpdateByte(b); } } - private void doCanonicalUpdateByte( - byte b) + private void DoCanonicalUpdateByte(byte b) { if (b == '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } else if (b == '\n') { if (lastb != '\r') { - doUpdateCRLF(); + DoUpdateCRLF(); } } else { - doUpdateByte(b); + DoUpdateByte(b); } lastb = b; } - private void doUpdateCRLF() + private void DoUpdateCRLF() { - doUpdateByte((byte)'\r'); - doUpdateByte((byte)'\n'); + DoUpdateByte((byte)'\r'); + DoUpdateByte((byte)'\n'); } - private void doUpdateByte( + private void DoUpdateByte( byte b) { sig.Update(b); dig.Update(b); } - public void Update( - byte[] b) + public void Update(params byte[] b) + { + Update(b, 0, b.Length); + } + + public void Update(byte[] b, int off, int len) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Update(b.AsSpan(off, len)); +#else if (signatureType == PgpSignature.CanonicalTextDocument) { - for (int i = 0; i != b.Length; i++) + int finish = off + len; + + for (int i = off; i != finish; i++) { - doCanonicalUpdateByte(b[i]); + DoCanonicalUpdateByte(b[i]); } } else { - sig.BlockUpdate(b, 0, b.Length); - dig.BlockUpdate(b, 0, b.Length); + sig.BlockUpdate(b, off, len); + dig.BlockUpdate(b, off, len); } +#endif } - public void Update( - byte[] b, - int off, - int len) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Update(ReadOnlySpan input) { if (signatureType == PgpSignature.CanonicalTextDocument) { - int finish = off + len; - - for (int i = off; i != finish; i++) + for (int i = 0; i < input.Length; ++i) { - doCanonicalUpdateByte(b[i]); + DoCanonicalUpdateByte(input[i]); } } else { - sig.BlockUpdate(b, off, len); - dig.BlockUpdate(b, off, len); + sig.BlockUpdate(input); + dig.BlockUpdate(input); } } +#endif - /// Return the one pass header associated with the current signature. + /// Return the one pass header associated with the current signature. public PgpOnePassSignature GenerateOnePassVersion( bool isNested) { diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs index 5d992ec51..e1405f481 100644 --- a/crypto/src/openpgp/Rfc6637Utilities.cs +++ b/crypto/src/openpgp/Rfc6637Utilities.cs @@ -68,12 +68,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } public static byte[] CreateKey(PublicKeyPacket pubKeyData, ECPoint s) + { + return CreateKey(pubKeyData, s.AffineXCoord.GetEncoded()); + } + + public static byte[] CreateKey(PublicKeyPacket pubKeyData, byte[] secret) { byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData); ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key; - return Kdf(ecKey.HashAlgorithm, s, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial); + return Kdf(ecKey.HashAlgorithm, secret, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial); } // RFC 6637 - Section 8 @@ -116,12 +121,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // ZB = x; // MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param ); // return oBits leftmost bits of MB. - private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters) + private static byte[] Kdf(HashAlgorithmTag digestAlg, byte[] ZB, int keyLen, byte[] parameters) { - byte[] ZB = s.XCoord.GetEncoded(); - - string digestName = PgpUtilities.GetDigestName(digestAlg); - IDigest digest = DigestUtilities.GetDigest(digestName); + IDigest digest = PgpUtilities.CreateDigest(digestAlg); digest.Update(0x00); digest.Update(0x00); -- cgit 1.4.1