using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Utilities.IO;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
/// Basic utility class.
public sealed class PgpUtilities
{
private PgpUtilities()
{
}
public static MPInteger[] DsaSigToMpi(
byte[] encoding)
{
DerInteger i1, i2;
try
{
Asn1Sequence s = (Asn1Sequence) Asn1Object.FromByteArray(encoding);
i1 = (DerInteger) s[0];
i2 = (DerInteger) s[1];
}
catch (IOException e)
{
throw new PgpException("exception encoding signature", e);
}
return new MPInteger[]{ new MPInteger(i1.Value), new MPInteger(i2.Value) };
}
public static MPInteger[] RsaSigToMpi(
byte[] encoding)
{
return new MPInteger[]{ new MPInteger(new BigInteger(1, encoding)) };
}
public static string GetDigestName(
HashAlgorithmTag hashAlgorithm)
{
switch (hashAlgorithm)
{
case HashAlgorithmTag.Sha1:
return "SHA1";
case HashAlgorithmTag.MD2:
return "MD2";
case HashAlgorithmTag.MD5:
return "MD5";
case HashAlgorithmTag.RipeMD160:
return "RIPEMD160";
case HashAlgorithmTag.Sha224:
return "SHA224";
case HashAlgorithmTag.Sha256:
return "SHA256";
case HashAlgorithmTag.Sha384:
return "SHA384";
case HashAlgorithmTag.Sha512:
return "SHA512";
default:
throw new PgpException("unknown hash algorithm tag in GetDigestName: " + hashAlgorithm);
}
}
public static string GetSignatureName(
PublicKeyAlgorithmTag keyAlgorithm,
HashAlgorithmTag hashAlgorithm)
{
string encAlg;
switch (keyAlgorithm)
{
case PublicKeyAlgorithmTag.RsaGeneral:
case PublicKeyAlgorithmTag.RsaSign:
encAlg = "RSA";
break;
case PublicKeyAlgorithmTag.Dsa:
encAlg = "DSA";
break;
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;
default:
throw new PgpException("unknown algorithm tag in signature:" + keyAlgorithm);
}
return GetDigestName(hashAlgorithm) + "with" + encAlg;
}
public static string GetSymmetricCipherName(
SymmetricKeyAlgorithmTag algorithm)
{
switch (algorithm)
{
case SymmetricKeyAlgorithmTag.Null:
return null;
case SymmetricKeyAlgorithmTag.TripleDes:
return "DESEDE";
case SymmetricKeyAlgorithmTag.Idea:
return "IDEA";
case SymmetricKeyAlgorithmTag.Cast5:
return "CAST5";
case SymmetricKeyAlgorithmTag.Blowfish:
return "Blowfish";
case SymmetricKeyAlgorithmTag.Safer:
return "SAFER";
case SymmetricKeyAlgorithmTag.Des:
return "DES";
case SymmetricKeyAlgorithmTag.Aes128:
return "AES";
case SymmetricKeyAlgorithmTag.Aes192:
return "AES";
case SymmetricKeyAlgorithmTag.Aes256:
return "AES";
case SymmetricKeyAlgorithmTag.Twofish:
return "Twofish";
case SymmetricKeyAlgorithmTag.Camellia128:
return "Camellia";
case SymmetricKeyAlgorithmTag.Camellia192:
return "Camellia";
case SymmetricKeyAlgorithmTag.Camellia256:
return "Camellia";
default:
throw new PgpException("unknown symmetric algorithm: " + algorithm);
}
}
public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
{
int keySize;
switch (algorithm)
{
case SymmetricKeyAlgorithmTag.Des:
keySize = 64;
break;
case SymmetricKeyAlgorithmTag.Idea:
case SymmetricKeyAlgorithmTag.Cast5:
case SymmetricKeyAlgorithmTag.Blowfish:
case SymmetricKeyAlgorithmTag.Safer:
case SymmetricKeyAlgorithmTag.Aes128:
case SymmetricKeyAlgorithmTag.Camellia128:
keySize = 128;
break;
case SymmetricKeyAlgorithmTag.TripleDes:
case SymmetricKeyAlgorithmTag.Aes192:
case SymmetricKeyAlgorithmTag.Camellia192:
keySize = 192;
break;
case SymmetricKeyAlgorithmTag.Aes256:
case SymmetricKeyAlgorithmTag.Twofish:
case SymmetricKeyAlgorithmTag.Camellia256:
keySize = 256;
break;
default:
throw new PgpException("unknown symmetric algorithm: " + algorithm);
}
return keySize;
}
public static KeyParameter MakeKey(
SymmetricKeyAlgorithmTag algorithm,
byte[] keyBytes)
{
string algName = GetSymmetricCipherName(algorithm);
return ParameterUtilities.CreateKeyParameter(algName, keyBytes);
}
public static KeyParameter MakeRandomKey(
SymmetricKeyAlgorithmTag algorithm,
SecureRandom random)
{
int keySize = GetKeySize(algorithm);
byte[] keyBytes = new byte[(keySize + 7) / 8];
random.NextBytes(keyBytes);
return MakeKey(algorithm, keyBytes);
}
internal static byte[] EncodePassPhrase(char[] passPhrase, bool utf8)
{
return passPhrase == null
? null
: utf8
? Encoding.UTF8.GetBytes(passPhrase)
: Strings.ToByteArray(passPhrase);
}
///
/// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
/// the historical behaviour of the library (1.7 and earlier).
///
public static KeyParameter MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase)
{
return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, false), true);
}
///
/// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
///
public static KeyParameter MakeKeyFromPassPhraseUtf8(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase)
{
return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, true), true);
}
///
/// Allows the caller to handle the encoding of the passphrase to bytes.
///
public static KeyParameter MakeKeyFromPassPhraseRaw(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase)
{
return DoMakeKeyFromPassPhrase(algorithm, s2k, rawPassPhrase, false);
}
internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase, bool clearPassPhrase)
{
int keySize = GetKeySize(algorithm);
byte[] pBytes = rawPassPhrase;
byte[] keyBytes = new byte[(keySize + 7) / 8];
int generatedBytes = 0;
int loopCount = 0;
while (generatedBytes < keyBytes.Length)
{
IDigest digest;
if (s2k != null)
{
string digestName = GetDigestName(s2k.HashAlgorithm);
try
{
digest = DigestUtilities.GetDigest(digestName);
}
catch (Exception e)
{
throw new PgpException("can't find S2k digest", e);
}
for (int i = 0; i != loopCount; i++)
{
digest.Update(0);
}
byte[] iv = s2k.GetIV();
switch (s2k.Type)
{
case S2k.Simple:
digest.BlockUpdate(pBytes, 0, pBytes.Length);
break;
case S2k.Salted:
digest.BlockUpdate(iv, 0, iv.Length);
digest.BlockUpdate(pBytes, 0, pBytes.Length);
break;
case S2k.SaltedAndIterated:
long count = s2k.IterationCount;
digest.BlockUpdate(iv, 0, iv.Length);
digest.BlockUpdate(pBytes, 0, pBytes.Length);
count -= iv.Length + pBytes.Length;
while (count > 0)
{
if (count < iv.Length)
{
digest.BlockUpdate(iv, 0, (int)count);
break;
}
else
{
digest.BlockUpdate(iv, 0, iv.Length);
count -= iv.Length;
}
if (count < pBytes.Length)
{
digest.BlockUpdate(pBytes, 0, (int)count);
count = 0;
}
else
{
digest.BlockUpdate(pBytes, 0, pBytes.Length);
count -= pBytes.Length;
}
}
break;
default:
throw new PgpException("unknown S2k type: " + s2k.Type);
}
}
else
{
try
{
digest = DigestUtilities.GetDigest("MD5");
for (int i = 0; i != loopCount; i++)
{
digest.Update(0);
}
digest.BlockUpdate(pBytes, 0, pBytes.Length);
}
catch (Exception e)
{
throw new PgpException("can't find MD5 digest", e);
}
}
byte[] dig = DigestUtilities.DoFinal(digest);
if (dig.Length > (keyBytes.Length - generatedBytes))
{
Array.Copy(dig, 0, keyBytes, generatedBytes, keyBytes.Length - generatedBytes);
}
else
{
Array.Copy(dig, 0, keyBytes, generatedBytes, dig.Length);
}
generatedBytes += dig.Length;
loopCount++;
}
if (clearPassPhrase && rawPassPhrase != null)
{
Array.Clear(rawPassPhrase, 0, rawPassPhrase.Length);
}
return MakeKey(algorithm, keyBytes);
}
#if !PORTABLE || DOTNET
/// Write out the passed in file as a literal data packet.
public static void WriteFileToLiteralData(
Stream output,
char fileType,
FileInfo file)
{
PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
Stream pOut = lData.Open(output, fileType, file.Name, file.Length, file.LastWriteTime);
PipeFileContents(file, pOut, 4096);
}
/// Write out the passed in file as a literal data packet in partial packet format.
public static void WriteFileToLiteralData(
Stream output,
char fileType,
FileInfo file,
byte[] buffer)
{
PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
Stream pOut = lData.Open(output, fileType, file.Name, file.LastWriteTime, buffer);
PipeFileContents(file, pOut, buffer.Length);
}
private static void PipeFileContents(FileInfo file, Stream pOut, int bufSize)
{
FileStream inputStream = file.OpenRead();
byte[] buf = new byte[bufSize];
int len;
while ((len = inputStream.Read(buf, 0, buf.Length)) > 0)
{
pOut.Write(buf, 0, len);
}
Platform.Dispose(pOut);
Platform.Dispose(inputStream);
}
#endif
private const int ReadAhead = 60;
private static bool IsPossiblyBase64(
int ch)
{
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
|| (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
|| (ch == '\r') || (ch == '\n');
}
///
/// Return either an ArmoredInputStream or a BcpgInputStream based on whether
/// the initial characters of the stream are binary PGP encodings or not.
///
public static Stream GetDecoderStream(
Stream inputStream)
{
// TODO Remove this restriction?
if (!inputStream.CanSeek)
throw new ArgumentException("inputStream must be seek-able", "inputStream");
long markedPos = inputStream.Position;
int ch = inputStream.ReadByte();
if ((ch & 0x80) != 0)
{
inputStream.Position = markedPos;
return inputStream;
}
else
{
if (!IsPossiblyBase64(ch))
{
inputStream.Position = markedPos;
return new ArmoredInputStream(inputStream);
}
byte[] buf = new byte[ReadAhead];
int count = 1;
int index = 1;
buf[0] = (byte)ch;
while (count != ReadAhead && (ch = inputStream.ReadByte()) >= 0)
{
if (!IsPossiblyBase64(ch))
{
inputStream.Position = markedPos;
return new ArmoredInputStream(inputStream);
}
if (ch != '\n' && ch != '\r')
{
buf[index++] = (byte)ch;
}
count++;
}
inputStream.Position = markedPos;
//
// nothing but new lines, little else, assume regular armoring
//
if (count < 4)
{
return new ArmoredInputStream(inputStream);
}
//
// test our non-blank data
//
byte[] firstBlock = new byte[8];
Array.Copy(buf, 0, firstBlock, 0, firstBlock.Length);
byte[] decoded = Base64.Decode(firstBlock);
//
// it's a base64 PGP block.
//
bool hasHeaders = (decoded[0] & 0x80) == 0;
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);
}
}
internal static byte[] GenerateIV(int length, SecureRandom random)
{
byte[] iv = new byte[length];
random.NextBytes(iv);
return iv;
}
internal static S2k GenerateS2k(HashAlgorithmTag hashAlgorithm, int s2kCount, SecureRandom random)
{
byte[] iv = GenerateIV(8, random);
return new S2k(hashAlgorithm, iv, s2kCount);
}
}
}