#region Disclaimer / License // Copyright (C) 2012, Jackie Ng // http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.IO; using OSGeo.MapGuide.ObjectModels.FeatureSource; namespace OSGeo.MapGuide.MaestroAPI { using Resource; public static class FeatureSourceCredentialExtensions { /// /// Sets the encrypted credentials for this Feature Source, the credentials are referenced with the %MG_USERNAME% /// and %MG_PASSWORD% placeholder tokens in the Feature Source content. /// /// /// /// public static void SetEncryptedCredentials(this IFeatureSource fs, string username, string password) { Check.NotNull(fs, "fs"); if (string.IsNullOrEmpty(fs.ResourceID)) throw new ArgumentException("Feature Source has no resource ID attached"); //LOCALIZEME using (var stream = CredentialWriter.Write(username, password)) { fs.SetResourceData("MG_USER_CREDENTIALS", ObjectModels.Common.ResourceDataType.String, stream); } } /// /// Gets the encrypted username referenced by the %MG_USERNAME% placeholder token in the Feature Source content /// /// /// public static string GetEncryptedUsername(this IFeatureSource fs) { Check.NotNull(fs, "fs"); var resData = fs.EnumerateResourceData(); foreach (var rd in resData) { if (rd.Name.ToUpper() == "MG_USER_CREDENTIALS") { using (var sr = new StreamReader(fs.GetResourceData("MG_USER_CREDENTIALS"))) { return sr.ReadToEnd(); } } } return null; } } /// /// Utility class to create encrypted Feature Source credentials /// public class CredentialWriter { //NOTE: This is a verbatim copy of MgCryptographyUtil in MgDev\Common\Security to the best I can do in .net //Only the encryption bits are implemented. Maestro has no need to decrypt MG_USER_CREDENTIALS //I'm sure this particular key isn't meant to be made public, but being able to correctly //write MG_USER_CREDENTIALS trumps this concern. Besides, if this key were to be truly private, it wouldn't be publicly visible //in the source code of a publicly accessible repository now would it? const string MG_CRYPTOGRAPHY_PRIVATE_KEY = "WutsokeedbA"; static readonly char[] MG_CRYPTOGRAPHY_DEC_CHARS = { '0','1','2','3','4','5','6','7','8','9' }; static readonly char[] MG_CRYPTOGRAPHY_HEX_CHARS = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; const int MG_CRYPTOGRAPHY_MAGIC_NUMBER_1 = 42; const int MG_CRYPTOGRAPHY_MAGIC_NUMBER_2 = 3; const int MG_CRYPTOGRAPHY_MIN_COLUMN_NUMBER = 5; const int MIN_CIPHER_TEXT_LENGTH = 34; const int MIN_KEY_LENGTH = 14; const int MAX_KEY_LENGTH = 32; const string STRING_DELIMITER = "\v"; const string RESERVED_CHARACTERS_STRINGS = "\v\f"; const string RESERVED_CHARACTERS_CREDENTIALS = "\t\r\n\v\f"; /// /// Encrypts the specified credentials. For a feature source that uses %MG_USERNAME% and %MG_PASSWORD% placeholder tokens to /// properly use these encrypted credentials, the encrypted string must be set as the MG_USER_CREDENTIALS resource data item /// for that feature source /// /// /// /// A containing the encrypted credentials public static Stream Write(string username, string password) { string credentials; EncryptStrings(username, password, out credentials, RESERVED_CHARACTERS_CREDENTIALS); return new MemoryStream(ASCIIEncoding.Default.GetBytes(credentials)); } static void EncryptStrings(string plainText1, string plainText2, out string cipherText, string reservedCharacters) { var reservedChars = reservedCharacters.ToCharArray(); if (plainText1.IndexOfAny(reservedChars) >= 0) { throw new ArgumentException("plainText1 contains reserved characters"); } if (plainText2.IndexOfAny(reservedChars) >= 0) { throw new ArgumentException("plainText2 contains reserved characters"); } string publicKey; GenerateCryptographKey(out publicKey); string tmpStr1, tmpStr2; CombineStrings(plainText1, plainText2, out tmpStr1); EncryptStringWithKey(tmpStr1, out tmpStr2, publicKey); CombineStrings(tmpStr2, publicKey, out tmpStr1); EncryptStringWithKey(tmpStr1, out tmpStr2, MG_CRYPTOGRAPHY_PRIVATE_KEY); EncryptStringByTransposition(tmpStr2, out cipherText); } static void EncryptStringByTransposition(string inStr, out string outStr) { string tmpStr; int inStrLength = inStr.Length; int numOfColumn = MG_CRYPTOGRAPHY_MIN_COLUMN_NUMBER; EncryptStringByTransposition(inStr, out tmpStr, numOfColumn); numOfColumn += inStrLength % 6; EncryptStringByTransposition(tmpStr, out outStr, numOfColumn); Debug.Assert(inStrLength == outStr.Length); } static void EncryptStringByTransposition(string inStr, out string outStr, int numOfColumn) { int inStrLen = inStr.Length; int numOfRow = (int)Math.Ceiling((double)inStrLen / (double)numOfColumn); StringBuilder sb = new StringBuilder(); for (int currColumn = 0; currColumn < numOfColumn; ++currColumn) { for (int currRow = 0; currRow < numOfRow; ++currRow) { int inIdx = currColumn + currRow * numOfColumn; if (inIdx < inStrLen) { sb.Append(inStr[inIdx]); } } } outStr = sb.ToString(); } static void GenerateCryptographKey(out string publicKey) { DateTime dt = DateTime.UtcNow; publicKey = dt.ToString("yyyymmddHHmmss"); } static void CombineStrings(string str1, string str2, out string outStr) { outStr = str1; outStr += STRING_DELIMITER; outStr += str2; } static void EncryptStringWithKey(string inStr, out string outStr, string key) { char prevChar = Convert.ToChar(MG_CRYPTOGRAPHY_MAGIC_NUMBER_1); char currChar; int keyIdx = 0; int keyLen = key.Length; int outStrLen = inStr.Length; StringBuilder tmpStr = new StringBuilder(); for (int i = 0; i < outStrLen; ++i) { currChar = inStr[i]; char c = Convert.ToChar(currChar ^ key[keyIdx] ^ prevChar ^ ((i / MG_CRYPTOGRAPHY_MAGIC_NUMBER_2) % 255)); tmpStr.Append(c); prevChar = currChar; ++keyIdx; if (keyIdx >= keyLen) { keyIdx = 0; } } BinToHex(tmpStr.ToString(), out outStr); Debug.Assert((inStr.Length * 2) == outStr.Length); } static void BinToHex(string binStr, out string hexStr) { int binStrLen = binStr.Length; hexStr = ""; StringBuilder sb = new StringBuilder(); for (int i = 0; i < binStrLen; ++i) { int num = binStr[i]; for (int j = 1; j >= 0; --j) { char c = MG_CRYPTOGRAPHY_HEX_CHARS[(num >> j * 4) & 0xF]; sb.Append(c); } } hexStr = sb.ToString(); } } }