У меня есть служба веб-API ASP.NET 4.6, работающая как служба приложений Azure в одном плане службы приложений в одном регионе. Мы модифицируем эту службу, чтобы ее можно было развернуть в нескольких регионах с балансировщиком нагрузки впереди, и у каждого региона будет свой собственный план службы приложений. Поэтому нам необходимо убедиться, что мы используем один и тот же ключ компьютера в каждом плане службы приложений, чтобы предотвратить выход пользователей из системы, когда балансировщик нагрузки направляет их на разные серверы.
Наше приложение некоторое время работало с использованием машинного ключа, автоматически предоставляемого Azure в одном плане службы приложений. Чтобы избежать выхода всех наших клиентов из системы во время перехода, я планировал извлечь этот существующий машинный ключ, а затем развернуть его в новых планах службы приложений в других регионах. Звучит достаточно просто, верно?
Однако извлечение этого ключа оказывается проблемой.
Я пробовал решения, перечисленные здесь: Получение текущего ключа компьютера ASP.NET
Хотя каждый метод возвращает какой-либо ключ, этот ключ не соответствует ключу, фактически используемому для создания токенов-носителей или для защиты билетов токена обновления. Когда я развертываю эти ключи на других серверах, токены-носители по-прежнему считаются недействительными, и попытка использовать существующий токен обновления приводит к ответу invalid_grant
.
Кроме того, даже когда я вручную устанавливаю ключ компьютера в файле web.config (или во время выполнения, используя такой код, как this), ни один из извлеченных машинных ключей не соответствует машинному ключу, который я установил вручную, что является дополнительным доказательством того, что все, что они возвращают, на самом деле не является машинным ключом. использовался. Это верно как локально на моем компьютере для разработки, так и в Azure.
Для справки, это код (с изъятым секретом безопасности), который я использовал для извлечения ключей расшифровки и проверки тремя разными способами:
[DllImport(@"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll")]
internal static extern int EcbCallISAPI(IntPtr pECB, int iFunction, byte[] bufferIn, int sizeIn, byte[] bufferOut, int sizeOut);
[Route("machine-key-test")]
public async Task<JObject> GetMachineKeys()
{
return new JObject(
new JProperty("A", GetAdminData()),
new JProperty("B", GetAdminDataNoIsolateApps()),
new JProperty("C", GetAdminDataPre45()));
JObject GetAdminData()
{
string appPath = "/";
byte[] genKeys = new byte[1024];
byte[] autogenKeys = new byte[1024];
int res = EcbCallISAPI(IntPtr.Zero, 4, genKeys, genKeys.Length, autogenKeys, autogenKeys.Length);
if (res == 1)
{
// Same as above
int validationKeySize = 64;
int decryptionKeySize = 24;
byte[] validationKey = new byte[validationKeySize];
byte[] decryptionKey = new byte[decryptionKeySize];
Buffer.BlockCopy(autogenKeys, 0, validationKey, 0, validationKeySize);
Buffer.BlockCopy(autogenKeys, validationKeySize, decryptionKey, 0, decryptionKeySize);
int pathHash = StringComparer.InvariantCultureIgnoreCase.GetHashCode(appPath);
validationKey[0] = (byte)(pathHash & 0xff);
validationKey[1] = (byte)((pathHash & 0xff00) >> 8);
validationKey[2] = (byte)((pathHash & 0xff0000) >> 16);
validationKey[3] = (byte)((pathHash & 0xff000000) >> 24);
decryptionKey[0] = (byte)(pathHash & 0xff);
decryptionKey[1] = (byte)((pathHash & 0xff00) >> 8);
decryptionKey[2] = (byte)((pathHash & 0xff0000) >> 16);
decryptionKey[3] = (byte)((pathHash & 0xff000000) >> 24);
var decrptionKeyString = decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
var validationKeyString = validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
return new JObject(
new JProperty("D", decrptionKeyString),
new JProperty("V", validationKeyString));
}
return null;
}
JObject GetAdminDataNoIsolateApps()
{
string appPath = "/";
byte[] genKeys = new byte[1024];
byte[] autogenKeys = new byte[1024];
int res = EcbCallISAPI(IntPtr.Zero, 4, genKeys, genKeys.Length, autogenKeys, autogenKeys.Length);
if (res == 1)
{
// Same as above
int validationKeySize = 64;
int decryptionKeySize = 24;
byte[] validationKey = new byte[validationKeySize];
byte[] decryptionKey = new byte[decryptionKeySize];
Buffer.BlockCopy(autogenKeys, 0, validationKey, 0, validationKeySize);
Buffer.BlockCopy(autogenKeys, validationKeySize, decryptionKey, 0, decryptionKeySize);
var decrptionKeyString = decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
var validationKeyString = validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
return new JObject(
new JProperty("D", decrptionKeyString),
new JProperty("V", validationKeyString));
}
return null;
}
JObject GetAdminDataPre45()
{
// https://stackoverflow.com/a/35954339/37725
byte[] autogenKeys = (byte[]) typeof(HttpRuntime)
.GetField("s_autogenKeys", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
Type t = typeof(System.Web.Security.DefaultAuthenticationEventArgs).Assembly.GetType(
"System.Web.Security.Cryptography.MachineKeyMasterKeyProvider");
ConstructorInfo ctor = t.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
Type ckey = typeof(System.Web.Security.DefaultAuthenticationEventArgs).Assembly.GetType(
"System.Web.Security.Cryptography.CryptographicKey");
ConstructorInfo ckeyCtor = ckey.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0];
Object ckeyobj = ckeyCtor.Invoke(new object[] {autogenKeys});
object o = ctor.Invoke(new object[] {new MachineKeySection(), null, null, ckeyobj, null});
var encKey = t.GetMethod("GetEncryptionKey").Invoke(o, null);
byte[] encBytes = ckey.GetMethod("GetKeyMaterial").Invoke(encKey, null) as byte[];
var vldKey = t.GetMethod("GetValidationKey").Invoke(o, null);
byte[] vldBytes = ckey.GetMethod("GetKeyMaterial").Invoke(vldKey, null) as byte[];
string decryptionKey = BitConverter.ToString(encBytes);
decryptionKey = decryptionKey.Replace("-", "");
string validationKey = BitConverter.ToString(vldBytes);
validationKey = validationKey.Replace("-", "");
return new JObject(
new JProperty("D", decryptionKey),
new JProperty("V", validationKey));
}
}
И это пример вывода, который я получаю:
{
"A": {
"D": "b298ba4ef5e8421e178770f50ee5414dd0aa1698afc3169d",
"V": "b298ba4e3ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
},
"B": {
"D": "dc509c9af5e8421e178770f50ee5414dd0aa1698afc3169d",
"V": "84246e973ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
},
"C": {
"D": "A2EDFD4ECE75A91F8E38D62B569248B14CE9193DD42E543E0D4BA5C9E2BED912",
"V": "DC6144A79985DEF712FABC729871A79FF2CF0DD73CBA617C3764D234DA1B63AD"
}
}
Я пытался использовать каждый из этих наборов ключей по очереди, как без явной установки алгоритмов расшифровки и проверки, так и с указанием различных комбинаций алгоритмов, которые соответствуют длинам ключей, как определено здесь, но безуспешно.
И, как я уже сказал, ни один из этих ключей не будет соответствовать машинному ключу, который я устанавливаю вручную в моем web.config
или который я устанавливаю вручную в коде.
Вывод, к которому я прихожу, заключается в том, что я либо делаю что-то тривиально неправильное, либо мне придется заставить всех наших пользователей принудительно выйти из системы, изменив машинный ключ на новый на всех наших серверах. Я надеюсь, что кто-то сможет указать мне в правильном направлении.
ValidationKey
,DecryptionKey
,Decryption
иValidationAlgorithm
в качестве переменных среды, а затем использовал эту технику, чтобы установить их при запуске: blog.nilayparikh.com/azure/web/set-machinekey-on-azure-web-app 26.03.2021