diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2015-10-12 15:49:54 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2015-10-12 15:49:54 +0700 |
commit | 06ba713c9b19102310675a6c58e07c68d8efb3c7 (patch) | |
tree | 2d4e747d988f74abca2a5513713e4ff0e8ed8e69 /crypto/src/openpgp | |
parent | Add new file entries (diff) | |
download | BouncyCastle.NET-ed25519-06ba713c9b19102310675a6c58e07c68d8efb3c7.tar.xz |
Port of latest PGP tests and supporting code changes
Diffstat (limited to 'crypto/src/openpgp')
-rw-r--r-- | crypto/src/openpgp/PgpEncryptedDataGenerator.cs | 154 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpKeyPair.cs | 2 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpPad.cs | 45 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpPrivateKey.cs | 37 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpPublicKey.cs | 132 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpPublicKeyEncryptedData.cs | 140 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpSecretKey.cs | 235 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs | 23 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpSignatureSubpacketVector.cs | 12 | ||||
-rw-r--r-- | crypto/src/openpgp/PgpUtilities.cs | 29 | ||||
-rw-r--r-- | crypto/src/openpgp/Rfc6637Utilities.cs | 138 | ||||
-rw-r--r-- | crypto/src/openpgp/SXprUtilities.cs | 102 |
12 files changed, 863 insertions, 186 deletions
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; } + } + } + } +} |