using System; using System.Collections.Generic; using System.IO; using System.Text; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.EdEC; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; using Org.BouncyCastle.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 static readonly IDictionary NameToHashID = CreateNameToHashID(); private static readonly IDictionary OidToName = CreateOidToName(); private static IDictionary CreateNameToHashID() { var d = new Dictionary(StringComparer.OrdinalIgnoreCase); d.Add("sha1", HashAlgorithmTag.Sha1); d.Add("sha224", HashAlgorithmTag.Sha224); d.Add("sha256", HashAlgorithmTag.Sha256); d.Add("sha384", HashAlgorithmTag.Sha384); d.Add("sha512", HashAlgorithmTag.Sha512); d.Add("ripemd160", HashAlgorithmTag.RipeMD160); d.Add("rmd160", HashAlgorithmTag.RipeMD160); d.Add("md2", HashAlgorithmTag.MD2); d.Add("tiger", HashAlgorithmTag.Tiger192); d.Add("haval", HashAlgorithmTag.Haval5pass160); d.Add("md5", HashAlgorithmTag.MD5); return d; } private static IDictionary CreateOidToName() { var d = new Dictionary(); d.Add(EdECObjectIdentifiers.id_X25519, "Curve25519"); d.Add(EdECObjectIdentifiers.id_Ed25519, "Ed25519"); d.Add(SecObjectIdentifiers.SecP256r1, "NIST P-256"); d.Add(SecObjectIdentifiers.SecP384r1, "NIST P-384"); d.Add(SecObjectIdentifiers.SecP521r1, "NIST P-521"); return d; } public static MPInteger[] DsaSigToMpi( byte[] encoding) { DerInteger i1, i2; try { Asn1Sequence s = Asn1Sequence.GetInstance(encoding); i1 = DerInteger.GetInstance(s[0]); i2 = DerInteger.GetInstance(s[1]); } catch (Exception 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 int GetDigestIDForName(string name) { if (NameToHashID.TryGetValue(name, out var hashAlgorithmTag)) return (int)hashAlgorithmTag; throw new ArgumentException("unable to map " + name + " to a hash id", nameof(name)); } /** * Return the EC curve name for the passed in OID. * * @param oid the EC curve object identifier in the PGP key * @return a string representation of the OID. */ public static string GetCurveName(DerObjectIdentifier oid) { if (OidToName.TryGetValue(oid, out var name)) return name; // fall back return ECNamedCurveTable.GetName(oid); } 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.EdDsa_Legacy: encAlg = "EdDSA"; 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) { try { digest = CreateDigest(s2k.HashAlgorithm); } 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 = CreateDigest(HashAlgorithmTag.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); } /// 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(); using (var pOut = lData.Open(output, fileType, file.Name, file.Length, file.LastWriteTime)) { PipeFileContents(file, pOut); } } /// 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(); using (var pOut = lData.Open(output, fileType, file.Name, file.LastWriteTime, buffer)) { PipeFileContents(file, pOut, buffer.Length); } } private static void PipeFileContents(FileInfo file, Stream pOut) { PipeFileContents(file, pOut, Streams.DefaultBufferSize); } private static void PipeFileContents(FileInfo file, Stream pOut, int bufferSize) { using (var fileStream = file.OpenRead()) { Streams.CopyTo(fileStream, pOut, bufferSize); } } 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", nameof(inputStream)); long markedPos = inputStream.Position; int ch = inputStream.ReadByte(); if ((ch & 0x80) != 0) { inputStream.Position = markedPos; return inputStream; } 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); try { byte[] decoded = Base64.Decode(firstBlock); // // it's a base64 PGP block. // bool hasHeaders = (decoded[0] & 0x80) == 0; return new ArmoredInputStream(inputStream, hasHeaders); } catch (IOException) { throw; } catch (Exception e) { throw new IOException(e.Message); } } internal static IDigest CreateDigest(HashAlgorithmTag hashAlgorithm) { return DigestUtilities.GetDigest(GetDigestName(hashAlgorithm)); } internal static ISigner CreateSigner(PublicKeyAlgorithmTag publicKeyAlgorithm, HashAlgorithmTag hashAlgorithm, AsymmetricKeyParameter key) { switch (publicKeyAlgorithm) { case PublicKeyAlgorithmTag.EdDsa_Legacy: { ISigner signer; if (key is Ed25519PrivateKeyParameters || key is Ed25519PublicKeyParameters) { signer = new Ed25519Signer(); } else if (key is Ed448PrivateKeyParameters || key is Ed448PublicKeyParameters) { signer = new Ed448Signer(Arrays.EmptyBytes); } else { throw new InvalidOperationException(); } return new EdDsaSigner(signer, CreateDigest(hashAlgorithm)); } default: { return SignerUtilities.GetSigner(GetSignatureName(publicKeyAlgorithm, hashAlgorithm)); } } } internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm) { switch (encAlgorithm) { 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); } } }