Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

516 lines
12 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 2000, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // radpack.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines functions for packing and unpacking RADIUS packets.
  12. //
  13. // MODIFICATION HISTORY
  14. //
  15. // 02/01/2000 Original version.
  16. //
  17. ///////////////////////////////////////////////////////////////////////////////
  18. #include <proxypch.h>
  19. #include <align.h>
  20. #include <md5.h>
  21. #include <hmac.h>
  22. #include <radpack.h>
  23. // Avoid dependencies on ntrtl.h
  24. extern "C" ULONG __stdcall RtlRandom(PULONG seed);
  25. // Return the number of bytes required to round 'length' to a multiple of 16.
  26. inline ULONG GetPaddingLength16(ULONG length) throw ()
  27. {
  28. return ROUND_UP_COUNT(length, 16) - length;
  29. }
  30. // Returns 'true' if attr is a Microsoft VSA. The attribute must be of type 26.
  31. inline bool isMicrosoftVSA(const RadiusAttribute& attr) throw ()
  32. {
  33. return attr.length >= 6 && !memcmp(attr.value, "\x00\x00\x01\x37", 4);
  34. }
  35. // Pack a 16-bit integer into a buffer.
  36. inline void InsertUInt16(PBYTE p, USHORT value) throw ()
  37. {
  38. p[0] = (BYTE)(value >> 8);
  39. p[1] = (BYTE)value;
  40. }
  41. // Unpack a 16-bit integer into a buffer.
  42. inline USHORT ExtractUInt16(const BYTE* p) throw ()
  43. {
  44. return (USHORT)(p[0] << 8) | (USHORT)p[1];
  45. }
  46. // Returns the number of bytes of padding that should be added when packing the
  47. // attribute.
  48. ULONG
  49. WINAPI
  50. GetPaddingLength(
  51. const RadiusAttribute& attr
  52. ) throw ()
  53. {
  54. switch (attr.type)
  55. {
  56. case RADIUS_USER_PASSWORD:
  57. return GetPaddingLength16(attr.length);
  58. case RADIUS_TUNNEL_PASSWORD:
  59. // Subtract 1 byte for the tag and 2 for the salt.
  60. return GetPaddingLength16(attr.length - 3);
  61. case RADIUS_VENDOR_SPECIFIC:
  62. {
  63. if (isMicrosoftVSA(attr))
  64. {
  65. switch (attr.value[4])
  66. {
  67. case MS_CHAP_MPPE_SEND_KEYS:
  68. case MS_CHAP_MPPE_RECV_KEYS:
  69. // Vendor-Id = 4 bytes
  70. // Vendor-Type = 1 byte
  71. // Vendor-Length = 1 byte
  72. // Salt = 2 bytes
  73. return GetPaddingLength16(attr.length - 8);
  74. }
  75. }
  76. break;
  77. }
  78. }
  79. return 0;
  80. }
  81. // Struct describing how to encrypt an attribute.
  82. struct CryptParameters
  83. {
  84. BOOL encrypted;
  85. BOOL salted;
  86. ULONG offset;
  87. };
  88. // Returns information about how to encrypt/decrypt an attribute.
  89. VOID
  90. WINAPI
  91. GetCryptParameters(
  92. const RadiusAttribute& attr,
  93. CryptParameters& parms
  94. ) throw ()
  95. {
  96. memset(&parms, 0, sizeof(parms));
  97. switch (attr.type)
  98. {
  99. case RADIUS_USER_PASSWORD:
  100. {
  101. parms.encrypted = TRUE;
  102. break;
  103. }
  104. case RADIUS_TUNNEL_PASSWORD:
  105. {
  106. parms.encrypted = TRUE;
  107. parms.salted = TRUE;
  108. parms.offset = 1; // Skip the tag.
  109. break;
  110. }
  111. case RADIUS_VENDOR_SPECIFIC:
  112. {
  113. if (isMicrosoftVSA(attr))
  114. {
  115. switch (attr.value[4])
  116. {
  117. case MS_CHAP_MPPE_KEYS:
  118. {
  119. parms.encrypted = TRUE;
  120. parms.offset = 6; // Skip the VSA header.
  121. break;
  122. }
  123. case MS_CHAP_MPPE_SEND_KEYS:
  124. case MS_CHAP_MPPE_RECV_KEYS:
  125. {
  126. parms.encrypted = TRUE;
  127. parms.salted = TRUE;
  128. parms.offset = 6; // Skip the VSA header.
  129. break;
  130. }
  131. }
  132. }
  133. break;
  134. }
  135. }
  136. }
  137. ULONG
  138. WINAPI
  139. GetBufferSizeRequired(
  140. const RadiusPacket& packet,
  141. const RadiusAttribute* proxyState,
  142. BOOL alwaysSign
  143. ) throw ()
  144. {
  145. // We'll look for the signature as we iterate through the attributes.
  146. BOOL hasSignature = FALSE;
  147. // We always need 20 bytes for the header.
  148. ULONG nbyte = 20;
  149. // Iterate through the attributes.
  150. for (const RadiusAttribute* attr = packet.begin; attr != packet.end; ++attr)
  151. {
  152. nbyte += 2; // Two bytes for type & length.
  153. nbyte += attr->length;
  154. nbyte += GetPaddingLength(*attr);
  155. if (attr->type == RADIUS_SIGNATURE)
  156. {
  157. hasSignature = TRUE;
  158. }
  159. else if (attr->type == RADIUS_EAP_MESSAGE)
  160. {
  161. alwaysSign = TRUE;
  162. }
  163. }
  164. // Reserve space for the Proxy-State attribute.
  165. if (proxyState) { nbyte += proxyState->length + 2; }
  166. // Reserve space for the signature if necessary.
  167. if (alwaysSign && !hasSignature && packet.code == RADIUS_ACCESS_REQUEST)
  168. {
  169. nbyte += 18;
  170. }
  171. return nbyte <= 4096 ? nbyte : 0;
  172. }
  173. VOID
  174. WINAPI
  175. PackBuffer(
  176. const BYTE* secret,
  177. ULONG secretLength,
  178. RadiusPacket& packet,
  179. const RadiusAttribute* proxyState,
  180. BOOL alwaysSign,
  181. BYTE* buffer
  182. ) throw ()
  183. {
  184. // Set up a cursor into the buffer.
  185. BYTE* dst = buffer;
  186. // Pack the header.
  187. *dst++ = packet.code;
  188. *dst++ = packet.identifier;
  189. InsertUInt16(dst, packet.length);
  190. dst += 2;
  191. // Pack the authenticator.
  192. if (packet.code == RADIUS_ACCESS_REQUEST)
  193. {
  194. if (packet.authenticator != 0)
  195. {
  196. memcpy(dst, packet.authenticator, 16);
  197. }
  198. else
  199. {
  200. static ULONG seed;
  201. if (seed == 0)
  202. {
  203. FILETIME ft;
  204. GetSystemTimeAsFileTime(&ft);
  205. seed = ft.dwLowDateTime | ft.dwHighDateTime;
  206. }
  207. ULONG auth[4];
  208. auth[0] = RtlRandom(&seed);
  209. auth[1] = RtlRandom(&seed);
  210. auth[2] = RtlRandom(&seed);
  211. auth[3] = RtlRandom(&seed);
  212. memcpy(dst, auth, 16);
  213. }
  214. }
  215. else
  216. {
  217. memset(dst, 0, 16);
  218. }
  219. dst += 16;
  220. // We'll look for the signature as we iterate through the attributes.
  221. BYTE* signature = NULL;
  222. for (const RadiusAttribute* attr = packet.begin; attr != packet.end; ++attr)
  223. {
  224. // Pack the type.
  225. *dst++ = attr->type;
  226. // Pack the length.
  227. ULONG paddingLength = GetPaddingLength(*attr);
  228. ULONG valueLength = attr->length + paddingLength;
  229. *dst++ = (BYTE)(2 + valueLength);
  230. if (attr->type == RADIUS_SIGNATURE)
  231. {
  232. signature = dst;
  233. }
  234. else if (attr->type == RADIUS_EAP_MESSAGE)
  235. {
  236. alwaysSign = TRUE;
  237. }
  238. // Pack the value ...
  239. memcpy(dst, attr->value, attr->length);
  240. // ... and add the padding.
  241. memset(dst + attr->length, 0, paddingLength);
  242. // Do we need to encrypt this attribute ?
  243. CryptParameters parms;
  244. GetCryptParameters(*attr, parms);
  245. if (parms.encrypted)
  246. {
  247. // Yes.
  248. IASRadiusCrypt(
  249. TRUE,
  250. parms.salted,
  251. secret,
  252. secretLength,
  253. buffer + 4,
  254. dst + parms.offset,
  255. valueLength - parms.offset
  256. );
  257. }
  258. dst += valueLength;
  259. }
  260. // Add the Proxy-State
  261. if (proxyState)
  262. {
  263. *dst++ = proxyState->type;
  264. *dst++ = proxyState->length + 2;
  265. memcpy(dst, proxyState->value, proxyState->length);
  266. dst += proxyState->length;
  267. }
  268. if (packet.code == RADIUS_ACCESS_REQUEST)
  269. {
  270. if (alwaysSign)
  271. {
  272. // If we didn't find a signature, ...
  273. if (!signature)
  274. {
  275. // ... then add one at the end of the packet.
  276. *dst++ = RADIUS_SIGNATURE;
  277. *dst++ = 18;
  278. signature = dst;
  279. }
  280. // Compute the signature.
  281. memset(signature, 0, 16);
  282. HMACMD5_CTX context;
  283. HMACMD5Init(&context, (BYTE*)secret, secretLength);
  284. HMACMD5Update(&context, buffer, packet.length);
  285. HMACMD5Final(&context, signature);
  286. }
  287. }
  288. else
  289. {
  290. // For everything but Access-Request, we compute the authenticator.
  291. MD5_CTX context;
  292. MD5Init(&context);
  293. MD5Update(&context, buffer, packet.length);
  294. MD5Update(&context, secret, secretLength);
  295. MD5Final(&context);
  296. memcpy(buffer + 4, context.digest, 16);
  297. }
  298. }
  299. RadiusAttribute*
  300. WINAPI
  301. FindAttribute(
  302. const RadiusPacket& packet,
  303. BYTE type
  304. )
  305. {
  306. for (const RadiusAttribute* i = packet.begin; i != packet.end; ++i)
  307. {
  308. if (i->type == type) { return const_cast<RadiusAttribute*>(i); }
  309. }
  310. return NULL;
  311. }
  312. ULONG
  313. WINAPI
  314. GetAttributeCount(
  315. const BYTE* buffer,
  316. ULONG bufferLength
  317. ) throw ()
  318. {
  319. if (bufferLength >= 20 && ExtractUInt16(buffer + 2) == bufferLength)
  320. {
  321. ULONG count = 0;
  322. const BYTE* end = buffer + bufferLength;
  323. for (const BYTE* p = buffer + 20; p < end; p += p[1])
  324. {
  325. ++count;
  326. }
  327. if (p == end) { return count; }
  328. }
  329. return MALFORMED_PACKET;
  330. }
  331. VOID
  332. WINAPI
  333. UnpackBuffer(
  334. BYTE* buffer,
  335. ULONG bufferLength,
  336. RadiusPacket& packet
  337. ) throw ()
  338. {
  339. // Set up a cursor into the buffer.
  340. BYTE* src = buffer;
  341. packet.code = *src++;
  342. packet.identifier = *src++;
  343. packet.length = ExtractUInt16(src);
  344. src +=2;
  345. packet.authenticator = src;
  346. src += 16;
  347. RadiusAttribute* dst = packet.begin;
  348. const BYTE* end = buffer + bufferLength;
  349. while (src < end)
  350. {
  351. dst->type = *src++;
  352. dst->length = *src++ - 2;
  353. dst->value = src;
  354. src += dst->length;
  355. ++dst;
  356. }
  357. }
  358. BYTE*
  359. WINAPI
  360. FindRawAttribute(
  361. BYTE type,
  362. BYTE* buffer,
  363. ULONG bufferLength
  364. )
  365. {
  366. BYTE* end = buffer + bufferLength;
  367. for (BYTE* p = buffer + 20; p < buffer + bufferLength; p += p[1])
  368. {
  369. if (*p == type) { return p; }
  370. }
  371. return NULL;
  372. }
  373. AuthResult
  374. WINAPI
  375. AuthenticateAndDecrypt(
  376. const BYTE* requestAuthenticator,
  377. const BYTE* secret,
  378. ULONG secretLength,
  379. BYTE* buffer,
  380. ULONG bufferLength,
  381. RadiusPacket& packet
  382. ) throw ()
  383. {
  384. AuthResult result = AUTH_UNKNOWN;
  385. if (!requestAuthenticator) { requestAuthenticator = buffer + 4; }
  386. // Check the authenticator for everything but Access-Request.
  387. if (buffer[0] != RADIUS_ACCESS_REQUEST)
  388. {
  389. MD5_CTX context;
  390. MD5Init(&context);
  391. MD5Update(&context, buffer, 4);
  392. MD5Update(&context, requestAuthenticator, 16);
  393. MD5Update(&context, buffer + 20, bufferLength - 20);
  394. MD5Update(&context, secret, secretLength);
  395. MD5Final(&context);
  396. if (memcmp(context.digest, buffer + 4, 16))
  397. {
  398. return AUTH_BAD_AUTHENTICATOR;
  399. }
  400. result = AUTH_AUTHENTIC;
  401. }
  402. // Look for a signature.
  403. BYTE* signature = FindRawAttribute(
  404. RADIUS_SIGNATURE,
  405. buffer,
  406. bufferLength
  407. );
  408. if (signature)
  409. {
  410. if (signature[1] != 18) { return AUTH_BAD_SIGNATURE; }
  411. signature += 2;
  412. BYTE sent[16];
  413. memcpy(sent, signature, 16);
  414. memset(signature, 0, 16);
  415. HMACMD5_CTX context;
  416. HMACMD5Init(&context, (BYTE*)secret, secretLength);
  417. HMACMD5Update(&context, buffer, 4);
  418. HMACMD5Update(&context, (BYTE*)requestAuthenticator, 16);
  419. HMACMD5Update(&context, buffer + 20, bufferLength - 20);
  420. HMACMD5Final(&context, signature);
  421. if (memcmp(signature, sent, 16)) { return AUTH_BAD_SIGNATURE; }
  422. result = AUTH_AUTHENTIC;
  423. }
  424. else if (FindRawAttribute(RADIUS_EAP_MESSAGE, buffer, bufferLength))
  425. {
  426. return AUTH_MISSING_SIGNATURE;
  427. }
  428. // The buffer is authentic, so decrypt the attributes.
  429. for (const RadiusAttribute* attr = packet.begin; attr != packet.end; ++attr)
  430. {
  431. // Do we need to decrypt this attribute ?
  432. CryptParameters parms;
  433. GetCryptParameters(*attr, parms);
  434. if (parms.encrypted)
  435. {
  436. // Yes.
  437. IASRadiusCrypt(
  438. FALSE,
  439. parms.salted,
  440. secret,
  441. secretLength,
  442. requestAuthenticator,
  443. attr->value + parms.offset,
  444. attr->length - parms.offset
  445. );
  446. }
  447. }
  448. return result;
  449. }