[mapguide-commits] r6887 - in trunk/Tools/Maestro: MaestroAPITests OSGeo.MapGuide.MaestroAPI
svn_mapguide at osgeo.org
svn_mapguide at osgeo.org
Tue Jul 10 09:57:57 PDT 2012
Author: jng
Date: 2012-07-10 09:57:56 -0700 (Tue, 10 Jul 2012)
New Revision: 6887
Added:
trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/CredentialWriter.cs
Modified:
trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs
trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj
Log:
#2069: Phew! Yes we can encrypt MG_USER_CREDENTIALS in fully managed code. That's one half of the equation solved!
Modified: trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs
===================================================================
--- trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs 2012-07-10 12:32:08 UTC (rev 6886)
+++ trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs 2012-07-10 16:57:56 UTC (rev 6887)
@@ -33,6 +33,94 @@
[TestFixture(Ignore = TestControl.IgnoreHttpConnectionTests)]
public class HttpConnectionTests
{
+ //[Test]
+ public void TestEncryptedFeatureSourceCredentials()
+ {
+ //Sensitive data redacted, nevertheless you can test and verify this by filling in the
+ //blanks here and uncommenting the above [Test] attribute. If the test passes, credential encryption is working
+ string server = "";
+ string database = "";
+ string actualUser = "";
+ string actualPass = "";
+ string bogusUser = "foo";
+ string bogusPass = "bar";
+
+ var conn = ConnectionUtil.CreateTestHttpConnection();
+ string fsId = "Library://UnitTests/EncryptedCredentials.FeatureSource";
+ var fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SQLServerSpatial");
+ fs.SetConnectionProperty("Username", "%MG_USERNAME%");
+ fs.SetConnectionProperty("Password", "%MG_PASSWORD%");
+ fs.SetConnectionProperty("Service", server);
+ fs.SetConnectionProperty("DataStore", database);
+ fs.ResourceID = fsId;
+ conn.ResourceService.SaveResource(fs);
+
+ using (var ms = CredentialWriter.Write(actualUser, actualPass))
+ {
+ conn.ResourceService.SetResourceData(fsId, "MG_USER_CREDENTIALS", ResourceDataType.String, ms);
+ }
+
+ string result = conn.FeatureService.TestConnection(fsId);
+ Assert.AreEqual("TRUE", result.ToUpper());
+
+ //Test convenience method
+ fsId = "Library://UnitTests/EncryptedCredentials2.FeatureSource";
+ fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SQLServerSpatial");
+ fs.SetConnectionProperty("Username", "%MG_USERNAME%");
+ fs.SetConnectionProperty("Password", "%MG_PASSWORD%");
+ fs.SetConnectionProperty("Service", server);
+ fs.SetConnectionProperty("DataStore", database);
+ fs.ResourceID = fsId;
+ conn.ResourceService.SaveResource(fs);
+ fs.SetEncryptedCredentials(actualUser, actualPass);
+
+ result = conn.FeatureService.TestConnection(fsId);
+ Assert.AreEqual("TRUE", result.ToUpper());
+ Assert.AreEqual(actualUser, fs.GetEncryptedUsername());
+
+ //Do not set encrypted credentials
+ fsId = "Library://UnitTests/EncryptedCredentials3.FeatureSource";
+ fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SQLServerSpatial");
+ fs.SetConnectionProperty("Username", "%MG_USERNAME%");
+ fs.SetConnectionProperty("Password", "%MG_PASSWORD%");
+ fs.SetConnectionProperty("Service", server);
+ fs.SetConnectionProperty("DataStore", database);
+ fs.ResourceID = fsId;
+ conn.ResourceService.SaveResource(fs);
+
+ try
+ {
+ result = conn.FeatureService.TestConnection(fsId);
+ Assert.AreEqual("FALSE", result.ToUpper());
+ }
+ catch //Exception or false I can't remember, as long as the result is not "true"
+ {
+
+ }
+
+ //Encrypt credentials, but use bogus username/password
+ fsId = "Library://UnitTests/EncryptedCredentials4.FeatureSource";
+ fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SQLServerSpatial");
+ fs.SetConnectionProperty("Username", "%MG_USERNAME%");
+ fs.SetConnectionProperty("Password", "%MG_PASSWORD%");
+ fs.SetConnectionProperty("Service", server);
+ fs.SetConnectionProperty("DataStore", database);
+ fs.ResourceID = fsId;
+ conn.ResourceService.SaveResource(fs);
+ fs.SetEncryptedCredentials(bogusUser, bogusPass);
+
+ try
+ {
+ result = conn.FeatureService.TestConnection(fsId);
+ Assert.AreEqual("FALSE", result.ToUpper());
+ }
+ catch
+ {
+ //Exception or false I can't remember, as long as the result is not "true"
+ }
+ Assert.AreEqual(bogusUser, fs.GetEncryptedUsername());
+ }
+
[Test]
public void TestFeatureSourceCaching()
{
Added: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/CredentialWriter.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/CredentialWriter.cs (rev 0)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/CredentialWriter.cs 2012-07-10 16:57:56 UTC (rev 6887)
@@ -0,0 +1,240 @@
+#region Disclaimer / License
+// Copyright (C) 2012, Jackie Ng
+// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie at 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
+ {
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="fs"></param>
+ /// <param name="username"></param>
+ /// <param name="password"></param>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Gets the encrypted username referenced by the %MG_USERNAME% placeholder token in the Feature Source content
+ /// </summary>
+ /// <param name="fs"></param>
+ /// <returns></returns>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Utility class to create encrypted Feature Source credentials
+ /// </summary>
+ 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";
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="username"></param>
+ /// <param name="password"></param>
+ /// <returns>A <see cref="T:System.IO.Stream"/> containing the encrypted credentials</returns>
+ 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();
+ }
+ }
+}
Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj 2012-07-10 12:32:08 UTC (rev 6886)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj 2012-07-10 16:57:56 UTC (rev 6887)
@@ -213,6 +213,7 @@
<Compile Include="CoordinateSystem\ISimpleTransform.cs" />
<Compile Include="CoordinateSystem\MeterBasedCoordinateSystem.cs" />
<Compile Include="CoordinateSystem\NsDoc.cs" />
+ <Compile Include="CredentialWriter.cs" />
<Compile Include="CrossConnection\NsDoc.cs" />
<Compile Include="CrossConnection\ResourceRebaser.cs" />
<Compile Include="Exceptions\CustomPropertyNotFoundException.cs" />
More information about the mapguide-commits
mailing list