diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index f46f99d37..2a2e63961 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -3,10 +3,13 @@ using System.Collections;
using System.Diagnostics;
using System.IO;
+using Org.BouncyCastle.Asn1.X9;
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;
@@ -79,70 +82,133 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
: EncMethod
{
internal PgpPublicKey pubKey;
- internal BigInteger[] data;
+ internal byte[][] data;
- internal PubMethod(
- PgpPublicKey pubKey)
+ internal PubMethod(PgpPublicKey pubKey)
{
this.pubKey = pubKey;
}
- public override void AddSessionInfo(
- byte[] si,
+ public override void AddSessionInfo(
+ byte[] sessionInfo,
SecureRandom random)
{
- IBufferedCipher c;
+ byte[] encryptedSessionInfo = EncryptSessionInfo(sessionInfo, random);
+
+ this.data = ProcessSessionInfo(encryptedSessionInfo);
+ }
- switch (pubKey.Algorithm)
+ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
+ {
+ if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
{
- 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);
+ 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);
+ }
+
+ AsymmetricKeyParameter akp = pubKey.GetKey();
+ c.Init(true, new ParametersWithRandom(akp, random));
+ return c.DoFinal(sessionInfo);
}
- AsymmetricKeyParameter akp = pubKey.GetKey();
+ ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+
+ // 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;
+
+ ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
+ ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
+
+ KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
+
+ IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
+ w.Init(true, new ParametersWithRandom(key, random));
+
+ byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo);
+
+ byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length);
+ byte[] VB = new MPInteger(new BigInteger(1, ephPub.Q.GetEncoded(false))).GetEncoded();
- c.Init(true, new ParametersWithRandom(akp, random));
+ byte[] rv = new byte[VB.Length + 1 + C.Length];
- byte[] encKey = c.DoFinal(si);
+ Array.Copy(VB, 0, rv, 0, VB.Length);
+ rv[VB.Length] = (byte)C.Length;
+ Array.Copy(C, 0, rv, VB.Length + 1, C.Length);
- switch (pubKey.Algorithm)
+ return rv;
+ }
+
+ private byte[][] ProcessSessionInfo(byte[] encryptedSessionInfo)
+ {
+ byte[][] data;
+
+ switch (pubKey.Algorithm)
{
- case PublicKeyAlgorithmTag.RsaEncrypt:
- case PublicKeyAlgorithmTag.RsaGeneral:
- data = new BigInteger[]{ new BigInteger(1, encKey) };
- break;
- case PublicKeyAlgorithmTag.ElGamalEncrypt:
- case PublicKeyAlgorithmTag.ElGamalGeneral:
- int halfLength = encKey.Length / 2;
- data = new BigInteger[]
- {
- new BigInteger(1, encKey, 0, halfLength),
- new BigInteger(1, encKey, halfLength, halfLength)
- };
- break;
- default:
- throw new PgpException("unknown asymmetric algorithm: " + encAlgorithm);
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ data = new byte[][] { ConvertToEncodedMpi(encryptedSessionInfo) };
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ int halfLength = encryptedSessionInfo.Length / 2;
+ byte[] b1 = new byte[halfLength];
+ byte[] b2 = new byte[halfLength];
+
+ Array.Copy(encryptedSessionInfo, 0, b1, 0, halfLength);
+ Array.Copy(encryptedSessionInfo, halfLength, b2, 0, halfLength);
+
+ data = new byte[][] {
+ ConvertToEncodedMpi(b1),
+ ConvertToEncodedMpi(b2),
+ };
+ break;
+ case PublicKeyAlgorithmTag.ECDH:
+ data = new byte[][]{ encryptedSessionInfo };
+ break;
+ default:
+ throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
}
+
+ return data;
}
- public override void Encode(BcpgOutputStream pOut)
+ private byte[] ConvertToEncodedMpi(byte[] encryptedSessionInfo)
+ {
+ try
+ {
+ return new MPInteger(new BigInteger(1, encryptedSessionInfo)).GetEncoded();
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("Invalid MPI encoding: " + e.Message, e);
+ }
+ }
+
+ public override void Encode(BcpgOutputStream pOut)
{
- PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(
- pubKey.KeyId, pubKey.Algorithm, data);
+ PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data);
- pOut.WritePacket(pk);
+ pOut.WritePacket(pk);
}
}
diff --git a/crypto/src/openpgp/PgpKeyPair.cs b/crypto/src/openpgp/PgpKeyPair.cs
index 6efb03a42..9cf78fa6f 100644
--- a/crypto/src/openpgp/PgpKeyPair.cs
+++ b/crypto/src/openpgp/PgpKeyPair.cs
@@ -34,7 +34,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
DateTime time)
{
this.pub = new PgpPublicKey(algorithm, pubKey, time);
- this.priv = new PgpPrivateKey(privKey, pub.KeyId);
+ this.priv = new PgpPrivateKey(pub.KeyId, pub.PublicKeyPacket, privKey);
}
/// <summary>Create a key pair from a PgpPrivateKey and a PgpPublicKey.</summary>
diff --git a/crypto/src/openpgp/PgpPad.cs b/crypto/src/openpgp/PgpPad.cs
new file mode 100644
index 000000000..48f7f2f44
--- /dev/null
+++ b/crypto/src/openpgp/PgpPad.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Padding functions.</remarks>
+ public sealed class PgpPad
+ {
+ private PgpPad()
+ {
+ }
+
+ public static byte[] PadSessionData(byte[] sessionInfo)
+ {
+ byte[] result = new byte[40];
+
+ Array.Copy(sessionInfo, 0, result, 0, sessionInfo.Length);
+
+ byte padValue = (byte)(result.Length - sessionInfo.Length);
+
+ for (int i = sessionInfo.Length; i != result.Length; i++)
+ {
+ result[i] = padValue;
+ }
+
+ return result;
+ }
+
+ public static byte[] UnpadSessionData(byte[] encoded)
+ {
+ byte padValue = encoded[encoded.Length - 1];
+
+ for (int i = encoded.Length - padValue; i != encoded.Length; i++)
+ {
+ if (encoded[i] != padValue)
+ throw new PgpException("bad padding found in session data");
+ }
+
+ byte[] taggedKey = new byte[encoded.Length - padValue];
+
+ Array.Copy(encoded, 0, taggedKey, 0, taggedKey.Length);
+
+ return taggedKey;
+ }
+ }
+}
diff --git a/crypto/src/openpgp/PgpPrivateKey.cs b/crypto/src/openpgp/PgpPrivateKey.cs
index 154c87cd7..61487a5b2 100644
--- a/crypto/src/openpgp/PgpPrivateKey.cs
+++ b/crypto/src/openpgp/PgpPrivateKey.cs
@@ -7,33 +7,42 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
/// <remarks>General class to contain a private key for use with other OpenPGP objects.</remarks>
public class PgpPrivateKey
{
- private readonly long keyId;
+ private readonly long keyID;
+ private readonly PublicKeyPacket publicKeyPacket;
private readonly AsymmetricKeyParameter privateKey;
- /// <summary>
- /// Create a PgpPrivateKey from a regular private key and the ID of its
- /// associated public key.
+ /// <summary>
+ /// Create a PgpPrivateKey from a keyID, the associated public data packet, and a regular private key.
/// </summary>
- /// <param name="privateKey">Private key to use.</param>
- /// <param name="keyId">ID of the corresponding public key.</param>
- public PgpPrivateKey(
- AsymmetricKeyParameter privateKey,
- long keyId)
+ /// <param name="keyID">ID of the corresponding public key.</param>
+ /// <param name="publicKeyPacket">the public key data packet to be associated with this private key.</param>
+ /// <param name="privateKey">the private key data packet to be associated with this private key.</param>
+ public PgpPrivateKey(
+ long keyID,
+ PublicKeyPacket publicKeyPacket,
+ AsymmetricKeyParameter privateKey)
{
if (!privateKey.IsPrivate)
throw new ArgumentException("Expected a private key", "privateKey");
- this.privateKey = privateKey;
- this.keyId = keyId;
+ this.keyID = keyID;
+ this.publicKeyPacket = publicKeyPacket;
+ this.privateKey = privateKey;
}
- /// <summary>The keyId associated with the contained private key.</summary>
+ /// <summary>The keyId associated with the contained private key.</summary>
public long KeyId
{
- get { return keyId; }
+ get { return keyID; }
}
- /// <summary>The contained private key.</summary>
+ /// <summary>The public key packet associated with this private key, if available.</summary>
+ public PublicKeyPacket PublicKeyPacket
+ {
+ get { return publicKeyPacket; }
+ }
+
+ /// <summary>The contained private key.</summary>
public AsymmetricKeyParameter Key
{
get { return privateKey; }
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 5bde2c8fe..904e29913 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -2,10 +2,14 @@ using System;
using System.Collections;
using System.IO;
+using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
+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;
using Org.BouncyCastle.Utilities.Collections;
@@ -15,6 +19,54 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
/// <remarks>General class to handle a PGP public key object.</remarks>
public class PgpPublicKey
{
+ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
+ {
+ IBcpgKey key = publicPk.Key;
+ IDigest digest;
+
+ if (publicPk.Version <= 3)
+ {
+ RsaPublicBcpgKey rK = (RsaPublicBcpgKey)key;
+
+ try
+ {
+ digest = DigestUtilities.GetDigest("MD5");
+ UpdateDigest(digest, rK.Modulus);
+ UpdateDigest(digest, rK.PublicExponent);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("can't encode key components: " + e.Message, e);
+ }
+ }
+ else
+ {
+ try
+ {
+ byte[] kBytes = publicPk.GetEncodedContents();
+
+ digest = DigestUtilities.GetDigest("SHA1");
+
+ digest.Update(0x99);
+ digest.Update((byte)(kBytes.Length >> 8));
+ digest.Update((byte)kBytes.Length);
+ digest.BlockUpdate(kBytes, 0, kBytes.Length);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("can't encode key components: " + e.Message, e);
+ }
+ }
+
+ return DigestUtilities.DoFinal(digest);
+ }
+
+ private static void UpdateDigest(IDigest d, BigInteger b)
+ {
+ byte[] bytes = b.ToByteArrayUnsigned();
+ d.BlockUpdate(bytes, 0, bytes.Length);
+ }
+
private static readonly int[] MasterKeyCertificationTypes = new int[]
{
PgpSignature.PositiveCertification,
@@ -39,51 +91,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
{
IBcpgKey key = publicPk.Key;
+ this.fingerprint = CalculateFingerprint(publicPk);
+
if (publicPk.Version <= 3)
{
RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
this.keyId = rK.Modulus.LongValue;
-
- try
- {
- IDigest digest = DigestUtilities.GetDigest("MD5");
-
- byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
- digest.BlockUpdate(bytes, 0, bytes.Length);
-
- bytes = rK.PublicExponent.ToByteArrayUnsigned();
- digest.BlockUpdate(bytes, 0, bytes.Length);
-
- this.fingerprint = DigestUtilities.DoFinal(digest);
- }
- //catch (NoSuchAlgorithmException)
- catch (Exception e)
- {
- throw new IOException("can't find MD5", e);
- }
-
this.keyStrength = rK.Modulus.BitLength;
}
else
{
- byte[] kBytes = publicPk.GetEncodedContents();
-
- try
- {
- IDigest digest = DigestUtilities.GetDigest("SHA1");
-
- digest.Update(0x99);
- digest.Update((byte)(kBytes.Length >> 8));
- digest.Update((byte)kBytes.Length);
- digest.BlockUpdate(kBytes, 0, kBytes.Length);
- this.fingerprint = DigestUtilities.DoFinal(digest);
- }
- catch (Exception e)
- {
- throw new IOException("can't find SHA1", e);
- }
-
this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
| ((ulong)fingerprint[fingerprint.Length - 7] << 48)
| ((ulong)fingerprint[fingerprint.Length - 6] << 40)
@@ -107,7 +125,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
}
else if (key is ECPublicBcpgKey)
{
- this.keyStrength = ECNamedCurveTable.GetByOid(((ECPublicBcpgKey)key).CurveOid).Curve.FieldSize;
+ this.keyStrength = ECKeyPairGenerator.FindECCurveByOid(((ECPublicBcpgKey)key).CurveOid).Curve.FieldSize;
}
}
}
@@ -146,6 +164,23 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
}
+ else if (pubKey is ECPublicKeyParameters)
+ {
+ ECPublicKeyParameters ecK = (ECPublicKeyParameters)pubKey;
+
+ if (algorithm == PublicKeyAlgorithmTag.ECDH)
+ {
+ bcpgKey = new ECDHPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q, HashAlgorithmTag.Sha256, SymmetricKeyAlgorithmTag.Aes128);
+ }
+ else if (algorithm == PublicKeyAlgorithmTag.ECDsa)
+ {
+ bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q);
+ }
+ else
+ {
+ throw new PgpException("unknown EC algorithm");
+ }
+ }
else if (pubKey is ElGamalPublicKeyParameters)
{
ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
@@ -172,6 +207,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
}
}
+ public PgpPublicKey(PublicKeyPacket publicPk)
+ : this(publicPk, Platform.CreateArrayList(), Platform.CreateArrayList())
+ {
+ }
+
/// <summary>Constructor for a sub-key.</summary>
internal PgpPublicKey(
PublicKeyPacket publicPk,
@@ -426,14 +466,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
case PublicKeyAlgorithmTag.RsaEncrypt:
case PublicKeyAlgorithmTag.RsaGeneral:
case PublicKeyAlgorithmTag.RsaSign:
- RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey) publicPk.Key;
+ RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key;
return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
case PublicKeyAlgorithmTag.Dsa:
- DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey) publicPk.Key;
+ 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;
+ 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");
@@ -449,6 +493,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
}
}
+ private ECPublicKeyParameters GetECKey(string algorithm)
+ {
+ ECPublicBcpgKey ecK = (ECPublicBcpgKey)publicPk.Key;
+ X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(ecK.CurveOid);
+ ECPoint q = x9.Curve.DecodePoint(BigIntegers.AsUnsignedByteArray(ecK.EncodedPoint));
+ return new ECPublicKeyParameters(algorithm, q, ecK.CurveOid);
+ }
+
/// <summary>Allows enumeration of any user IDs associated with the key.</summary>
/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
public IEnumerable GetUserIds()
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index b6504cbcd..c2a351182 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -1,10 +1,13 @@
using System;
using System.IO;
+using Org.BouncyCastle.Asn1.X9;
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;
@@ -77,22 +80,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
PgpPrivateKey privKey)
{
- byte[] plain = fetchSymmetricKeyData(privKey);
+ byte[] sessionData = RecoverSessionData(privKey);
- return (SymmetricKeyAlgorithmTag) plain[0];
+ return (SymmetricKeyAlgorithmTag)sessionData[0];
}
- /// <summary>Return the decrypted data stream for the packet.</summary>
+ /// <summary>Return the decrypted data stream for the packet.</summary>
public Stream GetDataStream(
PgpPrivateKey privKey)
{
- byte[] plain = fetchSymmetricKeyData(privKey);
+ byte[] sessionData = RecoverSessionData(privKey);
- IBufferedCipher c2;
- string cipherName = PgpUtilities.GetSymmetricCipherName((SymmetricKeyAlgorithmTag) plain[0]);
+ if (!ConfirmCheckSum(sessionData))
+ throw new PgpKeyValidationException("key checksum failed");
+
+ SymmetricKeyAlgorithmTag symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0];
+ if (symmAlg == SymmetricKeyAlgorithmTag.Null)
+ return encData.GetInputStream();
+
+ IBufferedCipher cipher;
+ string cipherName = PgpUtilities.GetSymmetricCipherName(symmAlg);
string cName = cipherName;
- try
+ try
{
if (encData is SymmetricEncIntegrityPacket)
{
@@ -103,7 +113,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
cName += "/OpenPGPCFB/NoPadding";
}
- c2 = CipherUtilities.GetCipher(cName);
+ cipher = CipherUtilities.GetCipher(cName);
}
catch (PgpException e)
{
@@ -114,19 +124,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
throw new PgpException("exception creating cipher", e);
}
- if (c2 == null)
- return encData.GetInputStream();
-
- try
+ try
{
KeyParameter key = ParameterUtilities.CreateKeyParameter(
- cipherName, plain, 1, plain.Length - 3);
+ cipherName, sessionData, 1, sessionData.Length - 3);
- byte[] iv = new byte[c2.GetBlockSize()];
+ byte[] iv = new byte[cipher.GetBlockSize()];
- c2.Init(false, new ParametersWithIV(key, iv));
+ cipher.Init(false, new ParametersWithIV(key, iv));
- encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c2, null));
+ encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), cipher, null));
if (encData is SymmetricEncIntegrityPacket)
{
@@ -178,75 +185,88 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
}
}
- private byte[] fetchSymmetricKeyData(
- PgpPrivateKey privKey)
+ private byte[] RecoverSessionData(PgpPrivateKey privKey)
{
- IBufferedCipher c1 = GetKeyCipher(keyData.Algorithm);
+ byte[][] secKeyData = keyData.GetEncSessionKey();
+
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.ECDH)
+ {
+ ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
+ X9ECParameters x9Params = ECKeyPairGenerator.FindECCurveByOid(ecKey.CurveOid);
+
+ byte[] enc = secKeyData[0];
+
+ int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+ byte[] pEnc = new byte[pLen];
+
+ Array.Copy(enc, 2, pEnc, 0, pLen);
+
+ byte[] keyEnc = new byte[enc[pLen + 2]];
+
+ Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
+
+ ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc);
+
+ ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key;
+ ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize();
+
+ KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S));
+
+ IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
+ w.Init(false, key);
- try
+ return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length));
+ }
+
+ IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
+
+ try
{
- c1.Init(false, privKey.Key);
+ cipher.Init(false, privKey.Key);
}
catch (InvalidKeyException e)
{
throw new PgpException("error setting asymmetric cipher", e);
}
- BigInteger[] keyD = keyData.GetEncSessionKey();
-
- if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
|| keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
{
- c1.ProcessBytes(keyD[0].ToByteArrayUnsigned());
+ 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;
- byte[] bi = keyD[0].ToByteArray();
-
- int diff = bi.Length - size;
- if (diff >= 0)
- {
- c1.ProcessBytes(bi, diff, size);
- }
- else
- {
- byte[] zeros = new byte[-diff];
- c1.ProcessBytes(zeros);
- c1.ProcessBytes(bi);
- }
-
- bi = keyD[1].ToByteArray();
-
- diff = bi.Length - size;
- if (diff >= 0)
- {
- c1.ProcessBytes(bi, diff, size);
- }
- else
- {
- byte[] zeros = new byte[-diff];
- c1.ProcessBytes(zeros);
- c1.ProcessBytes(bi);
- }
+ ProcessEncodedMpi(cipher, size, secKeyData[0]);
+ ProcessEncodedMpi(cipher, size, secKeyData[1]);
}
- byte[] plain;
- try
+ try
{
- plain = c1.DoFinal();
+ return cipher.DoFinal();
}
catch (Exception e)
{
throw new PgpException("exception decrypting secret key", e);
}
-
- if (!ConfirmCheckSum(plain))
- throw new PgpKeyValidationException("key checksum failed");
-
- return plain;
}
+
+ private static void ProcessEncodedMpi(IBufferedCipher cipher, int size, byte[] mpiEnc)
+ {
+ if (mpiEnc.Length - 2 > size) // leading Zero? Shouldn't happen but...
+ {
+ cipher.ProcessBytes(mpiEnc, 3, mpiEnc.Length - 3);
+ }
+ else
+ {
+ byte[] tmp = new byte[size];
+ Array.Copy(mpiEnc, 2, tmp, tmp.Length - (mpiEnc.Length - 2), mpiEnc.Length - 2);
+ cipher.ProcessBytes(tmp, 0, tmp.Length);
+ }
+ }
}
}
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 872316dd7..980f9222b 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -2,8 +2,11 @@ using System;
using System.Collections;
using System.IO;
+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.Security;
using Org.BouncyCastle.Utilities;
@@ -59,6 +62,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
DsaPrivateKeyParameters dsK = (DsaPrivateKeyParameters) privKey.Key;
secKey = new DsaSecretBcpgKey(dsK.X);
break;
+ case PublicKeyAlgorithmTag.ECDH:
+ case PublicKeyAlgorithmTag.ECDsa:
+ ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
+ secKey = new ECSecretBcpgKey(ecK.D);
+ break;
case PublicKeyAlgorithmTag.ElGamalEncrypt:
case PublicKeyAlgorithmTag.ElGamalGeneral:
ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
@@ -309,24 +317,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
private byte[] ExtractKeyData(
char[] passPhrase)
{
- SymmetricKeyAlgorithmTag alg = secret.EncAlgorithm;
+ SymmetricKeyAlgorithmTag encAlgorithm = secret.EncAlgorithm;
byte[] encData = secret.GetSecretKeyData();
- if (alg == SymmetricKeyAlgorithmTag.Null)
+ if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
// TODO Check checksum here?
return encData;
- IBufferedCipher c = null;
- try
- {
- string cName = PgpUtilities.GetSymmetricCipherName(alg);
- c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
- }
- catch (Exception e)
- {
- throw new PgpException("Exception creating cipher", e);
- }
-
// TODO Factor this block out as 'decryptData'
try
{
@@ -336,9 +333,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
if (secret.PublicKeyPacket.Version >= 4)
{
- c.Init(false, new ParametersWithIV(key, iv));
-
- data = c.DoFinal(encData);
+ data = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, 0, encData.Length);
bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1;
byte[] check = Checksum(useSha1, data, (useSha1) ? data.Length - 20 : data.Length - 2);
@@ -364,15 +359,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
for (int i = 0; i != 4; i++)
{
- c.Init(false, new ParametersWithIV(key, iv));
-
int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
data[pos] = encData[pos];
data[pos + 1] = encData[pos + 1];
pos += 2;
- c.DoFinal(encData, pos, encLen, data, pos);
+ byte[] tmp = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, pos, encLen);
+ Array.Copy(tmp, 0, data, pos, encLen);
pos += encLen;
if (i != 3)
@@ -416,6 +410,25 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
}
}
+ private static byte[] RecoverKeyData(SymmetricKeyAlgorithmTag encAlgorithm, string modeAndPadding,
+ KeyParameter key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ {
+ IBufferedCipher c;
+ try
+ {
+ string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+ c = CipherUtilities.GetCipher(cName + modeAndPadding);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+
+ c.Init(false, new ParametersWithIV(key, iv));
+
+ return c.DoFinal(keyData, keyOff, keyLen);
+ }
+
/// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
public PgpPrivateKey ExtractPrivateKey(
char[] passPhrase)
@@ -453,6 +466,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
DsaParameters dsaParams = new DsaParameters(dsaPub.P, dsaPub.Q, dsaPub.G);
privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
break;
+ case PublicKeyAlgorithmTag.ECDH:
+ privateKey = GetECKey("ECDH", bcpgIn);
+ break;
+ case PublicKeyAlgorithmTag.ECDsa:
+ privateKey = GetECKey("ECDSA", bcpgIn);
+ break;
case PublicKeyAlgorithmTag.ElGamalEncrypt:
case PublicKeyAlgorithmTag.ElGamalGeneral:
ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
@@ -464,7 +483,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
throw new PgpException("unknown public key algorithm encountered");
}
- return new PgpPrivateKey(privateKey, KeyId);
+ return new PgpPrivateKey(KeyId, pubPk, privateKey);
}
catch (PgpException e)
{
@@ -476,6 +495,13 @@ 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,
@@ -698,5 +724,174 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
return c.DoFinal(rawKeyData);
}
+
+ /**
+ * Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
+ *
+ * @return a secret key object.
+ */
+ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey)
+ {
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (type.Equals("protected-private-key"))
+ {
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string curveName;
+
+ string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (keyType.Equals("ecc"))
+ {
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+
+ SXprUtilities.SkipCloseParenthesis(inputStream);
+ }
+ else
+ {
+ throw new PgpException("no curve details found");
+ }
+
+ byte[] qVal;
+
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (type.Equals("q"))
+ {
+ qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+ }
+ else
+ {
+ throw new PgpException("no q value found");
+ }
+
+ SXprUtilities.SkipCloseParenthesis(inputStream);
+
+ byte[] dValue = GetDValue(inputStream, passPhrase, 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");
+ }
+
+ /**
+ * Parse a secret key from one of the GPG S expression keys.
+ *
+ * @return a secret key object.
+ */
+ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase)
+ {
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (type.Equals("protected-private-key"))
+ {
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string curveName;
+
+ string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (keyType.Equals("ecc"))
+ {
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+
+ if (curveName.StartsWith("NIST "))
+ {
+ curveName = curveName.Substring("NIST ".Length);
+ }
+
+ SXprUtilities.SkipCloseParenthesis(inputStream);
+ }
+ else
+ {
+ throw new PgpException("no curve details found");
+ }
+
+ byte[] qVal;
+
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (type.Equals("q"))
+ {
+ qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+ }
+ 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)));
+
+ SXprUtilities.SkipCloseParenthesis(inputStream);
+
+ byte[] dValue = GetDValue(inputStream, passPhrase, curveName);
+ // TODO: check SHA-1 hash.
+
+ return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null,
+ new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), new PgpPublicKey(pubPacket));
+ }
+
+ throw new PgpException("unknown key type found");
+ }
+
+ private static byte[] GetDValue(Stream inputStream, char[] passPhrase, string curveName)
+ {
+ string type;
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ string protection;
+ S2k s2k;
+ byte[] iv;
+ byte[] secKeyData;
+
+ type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+ if (type.Equals("protected"))
+ {
+ protection = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+
+ SXprUtilities.SkipOpenParenthesis(inputStream);
+
+ s2k = SXprUtilities.ParseS2k(inputStream);
+
+ iv = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+
+ SXprUtilities.SkipCloseParenthesis(inputStream);
+
+ secKeyData = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+ }
+ else
+ {
+ throw new PgpException("protected block not found");
+ }
+
+ // TODO: recognise other algorithms
+ KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, passPhrase);
+
+ byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+
+ //
+ // 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());
+ }
}
}
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
index 4adf64012..d2177d09c 100644
--- a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
@@ -25,7 +25,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
list.Add(new Exportable(isCritical, isExportable));
}
- /// <summary>
+ public void SetFeature(
+ bool isCritical,
+ byte feature)
+ {
+ list.Add(new Features(isCritical, feature));
+ }
+
+ /// <summary>
/// Add a TrustSignature packet to the signature. The values for depth and trust are largely
/// installation dependent but there are some guidelines in RFC 4880 - 5.2.3.13.
/// </summary>
@@ -117,7 +124,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
list.Add(new SignerUserId(isCritical, userId));
}
- public void SetEmbeddedSignature(
+ public void SetSignerUserId(
+ bool isCritical,
+ byte[] rawUserId)
+ {
+ if (rawUserId == null)
+ throw new ArgumentNullException("rawUserId");
+
+ list.Add(new SignerUserId(isCritical, false, rawUserId));
+ }
+
+ public void SetEmbeddedSignature(
bool isCritical,
PgpSignature pgpSignature)
{
@@ -136,7 +153,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length);
- list.Add(new EmbeddedSignature(isCritical, data));
+ list.Add(new EmbeddedSignature(isCritical, false, data));
}
public void SetPrimaryUserId(
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
index 68fe4b594..156243f4e 100644
--- a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
+++ b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
@@ -209,7 +209,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
return list;
}
- [Obsolete("Use 'Count' property instead")]
+ public Features GetFeatures()
+ {
+ SignatureSubpacket p = this.GetSubpacket(SignatureSubpacketTag.Features);
+
+ if (p == null)
+ return null;
+
+ return new Features(p.IsCritical(), p.IsLongLength(), p.GetData());
+ }
+
+ [Obsolete("Use 'Count' property instead")]
public int Size
{
get { return packets.Length; }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 32e37b819..e4551db07 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -86,7 +86,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
case PublicKeyAlgorithmTag.Dsa:
encAlg = "DSA";
break;
- case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
+ case PublicKeyAlgorithmTag.ECDH:
+ encAlg = "ECDH";
+ break;
+ case PublicKeyAlgorithmTag.ECDsa:
+ encAlg = "ECDSA";
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
case PublicKeyAlgorithmTag.ElGamalGeneral:
encAlg = "ElGamal";
break;
@@ -135,7 +141,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
}
}
- public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
+ public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
{
int keySize;
switch (algorithm)
@@ -193,7 +199,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
char[] passPhrase)
{
int keySize = GetKeySize(algorithm);
- byte[] pBytes = Strings.ToByteArray(new string(passPhrase));
+ byte[] pBytes = Encoding.UTF8.GetBytes(passPhrase);
byte[] keyBytes = new byte[(keySize + 7) / 8];
int generatedBytes = 0;
@@ -431,5 +437,22 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
return new ArmoredInputStream(inputStream, hasHeaders);
}
}
+
+ internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm)
+ {
+ switch (encAlgorithm)
+ {
+ case SymmetricKeyAlgorithmTag.Aes128:
+ case SymmetricKeyAlgorithmTag.Aes192:
+ case SymmetricKeyAlgorithmTag.Aes256:
+ return WrapperUtilities.GetWrapper("AESWRAP");
+ case SymmetricKeyAlgorithmTag.Camellia128:
+ case SymmetricKeyAlgorithmTag.Camellia192:
+ case SymmetricKeyAlgorithmTag.Camellia256:
+ return WrapperUtilities.GetWrapper("CAMELLIAWRAP");
+ default:
+ throw new PgpException("unknown wrap algorithm: " + encAlgorithm);
+ }
+ }
}
}
diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs
new file mode 100644
index 000000000..5d992ec51
--- /dev/null
+++ b/crypto/src/openpgp/Rfc6637Utilities.cs
@@ -0,0 +1,138 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public sealed class Rfc6637Utilities
+ {
+ private Rfc6637Utilities()
+ {
+ }
+
+ // "Anonymous Sender ", which is the octet sequence
+ private static readonly byte[] ANONYMOUS_SENDER = Hex.Decode("416E6F6E796D6F75732053656E64657220202020");
+
+ public static string GetAgreementAlgorithm(PublicKeyPacket pubKeyData)
+ {
+ ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
+
+ switch (ecKey.HashAlgorithm)
+ {
+ case HashAlgorithmTag.Sha256:
+ return "ECCDHwithSHA256CKDF";
+ case HashAlgorithmTag.Sha384:
+ return "ECCDHwithSHA384CKDF";
+ case HashAlgorithmTag.Sha512:
+ return "ECCDHwithSHA512CKDF";
+ default:
+ throw new ArgumentException("Unknown hash algorithm specified: " + ecKey.HashAlgorithm);
+ }
+ }
+
+ public static DerObjectIdentifier GetKeyEncryptionOID(SymmetricKeyAlgorithmTag algID)
+ {
+ switch (algID)
+ {
+ case SymmetricKeyAlgorithmTag.Aes128:
+ return NistObjectIdentifiers.IdAes128Wrap;
+ case SymmetricKeyAlgorithmTag.Aes192:
+ return NistObjectIdentifiers.IdAes192Wrap;
+ case SymmetricKeyAlgorithmTag.Aes256:
+ return NistObjectIdentifiers.IdAes256Wrap;
+ default:
+ throw new PgpException("unknown symmetric algorithm ID: " + algID);
+ }
+ }
+
+ public static int GetKeyLength(SymmetricKeyAlgorithmTag algID)
+ {
+ switch (algID)
+ {
+ case SymmetricKeyAlgorithmTag.Aes128:
+ return 16;
+ case SymmetricKeyAlgorithmTag.Aes192:
+ return 24;
+ case SymmetricKeyAlgorithmTag.Aes256:
+ return 32;
+ default:
+ throw new PgpException("unknown symmetric algorithm ID: " + 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);
+ }
+
+ // RFC 6637 - Section 8
+ // curve_OID_len = (byte)len(curve_OID);
+ // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03
+ // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous
+ // Sender " || recipient_fingerprint;
+ // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap
+ // Compute Z = KDF( S, Z_len, Param );
+ public static byte[] CreateUserKeyingMaterial(PublicKeyPacket pubKeyData)
+ {
+ MemoryStream pOut = new MemoryStream();
+ ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
+ byte[] encOid = ecKey.CurveOid.GetEncoded();
+
+ pOut.Write(encOid, 1, encOid.Length - 1);
+ pOut.WriteByte((byte)pubKeyData.Algorithm);
+ pOut.WriteByte(0x03);
+ pOut.WriteByte(0x01);
+ pOut.WriteByte((byte)ecKey.HashAlgorithm);
+ pOut.WriteByte((byte)ecKey.SymmetricKeyAlgorithm);
+ pOut.Write(ANONYMOUS_SENDER, 0, ANONYMOUS_SENDER.Length);
+
+ byte[] fingerprint = PgpPublicKey.CalculateFingerprint(pubKeyData);
+ pOut.Write(fingerprint, 0, fingerprint.Length);
+
+ return pOut.ToArray();
+ }
+
+ // RFC 6637 - Section 7
+ // Implements KDF( X, oBits, Param );
+ // Input: point X = (x,y)
+ // oBits - the desired size of output
+ // hBits - the size of output of hash function Hash
+ // Param - octets representing the parameters
+ // Assumes that oBits <= hBits
+ // Convert the point X to the octet string, see section 6:
+ // ZB' = 04 || x || y
+ // and extract the x portion from ZB'
+ // 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)
+ {
+ byte[] ZB = s.XCoord.GetEncoded();
+
+ string digestName = PgpUtilities.GetDigestName(digestAlg);
+ IDigest digest = DigestUtilities.GetDigest(digestName);
+
+ digest.Update(0x00);
+ digest.Update(0x00);
+ digest.Update(0x00);
+ digest.Update(0x01);
+ digest.BlockUpdate(ZB, 0, ZB.Length);
+ digest.BlockUpdate(parameters, 0, parameters.Length);
+
+ byte[] hash = DigestUtilities.DoFinal(digest);
+
+ return Arrays.CopyOfRange(hash, 0, keyLen);
+ }
+ }
+}
diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprUtilities.cs
new file mode 100644
index 000000000..68ff373a8
--- /dev/null
+++ b/crypto/src/openpgp/SXprUtilities.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /**
+ * Utility functions for looking a 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
+ * </p>
+ */
+ public sealed class SXprUtilities
+ {
+ private SXprUtilities()
+ {
+ }
+
+ private static int ReadLength(Stream input, int ch)
+ {
+ int len = ch - '0';
+
+ while ((ch = input.ReadByte()) >= 0 && ch != ':')
+ {
+ len = len * 10 + ch - '0';
+ }
+
+ return len;
+ }
+
+ internal static string ReadString(Stream input, int ch)
+ {
+ int len = ReadLength(input, ch);
+
+ char[] chars = new char[len];
+
+ for (int i = 0; i != chars.Length; i++)
+ {
+ chars[i] = (char)input.ReadByte();
+ }
+
+ return new string(chars);
+ }
+
+ internal static byte[] ReadBytes(Stream input, int ch)
+ {
+ int len = ReadLength(input, ch);
+
+ byte[] data = new byte[len];
+
+ Streams.ReadFully(input, data);
+
+ return data;
+ }
+
+ internal static S2k ParseS2k(Stream input)
+ {
+ SkipOpenParenthesis(input);
+
+ string alg = ReadString(input, input.ReadByte());
+ byte[] iv = ReadBytes(input, input.ReadByte());
+ long iterationCount = Int64.Parse(ReadString(input, input.ReadByte()));
+
+ SkipCloseParenthesis(input);
+
+ // we have to return the actual iteration count provided.
+ return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount);
+ }
+
+ internal static void SkipOpenParenthesis(Stream input)
+ {
+ int ch = input.ReadByte();
+ if (ch != '(')
+ throw new IOException("unknown character encountered");
+ }
+
+ internal static void SkipCloseParenthesis(Stream input)
+ {
+ int ch = input.ReadByte();
+ if (ch != ')')
+ throw new IOException("unknown character encountered");
+ }
+
+ private class MyS2k : S2k
+ {
+ private readonly long mIterationCount64;
+
+ internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64)
+ : base(algorithm, iv, (int)iterationCount64)
+ {
+ this.mIterationCount64 = iterationCount64;
+ }
+
+ public override long IterationCount
+ {
+ get { return mIterationCount64; }
+ }
+ }
+ }
+}
|