import from HEAD
[freeradius.git] / src / modules / rlm_otp / otp_mppe.c
1 /*
2  * $Id$
3  *
4  *   This program is free software; you can redistribute it and/or modify
5  *   it under the terms of the GNU General Public License as published by
6  *   the Free Software Foundation; either version 2 of the License, or
7  *   (at your option) any later version.
8  *
9  *   This program is distributed in the hope that it will be useful,
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *   GNU General Public License for more details.
13  *
14  *   You should have received a copy of the GNU General Public License
15  *   along with this program; if not, write to the Free Software
16  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  *
18  * Copyright 2001,2002  Google, Inc.
19  * Copyright 2005,2006 TRI-D Systems, Inc.
20  */
21
22 #include "ident.h"
23 RCSID("$Id$")
24
25 /* avoid inclusion of these FR headers which conflict w/ OpenSSL */
26 #define _LRAD_MD4_H
27 #define _LRAD_SHA1_H
28
29 #include <rad_assert.h>
30
31 #include "extern.h"
32 #include "otp.h"
33 #include "otp_mppe.h"
34
35 #include <openssl/des.h>
36 #include <openssl/md4.h>
37 #include <openssl/md5.h>
38 #include <openssl/sha.h>
39
40 #include <string.h>
41
42 /*
43  * Add MPPE attributes to a request, if required.
44  */
45 void
46 otp_mppe(REQUEST *request, otp_pwe_t pwe, const otp_option_t *opt,
47          const char *passcode)
48 {
49   VALUE_PAIR **avp = &request->reply->vps;
50   VALUE_PAIR *cvp, *rvp, *vp;
51
52   cvp = pairfind(request->packet->vps, pwattr[pwe - 1]);
53   rvp = pairfind(request->packet->vps, pwattr[pwe]);
54
55   switch (pwe) {
56   case PWE_PAP:
57   case PWE_CHAP:
58     return;
59
60   case PWE_MSCHAP:
61     /* First, set some related attributes. */
62     vp = pairmake("MS-MPPE-Encryption-Policy",
63                   otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ);
64     rad_assert(vp != NULL);
65     pairadd(avp, vp);
66     vp = pairmake("MS-MPPE-Encryption-Types",
67                   otp_mppe_types[opt->mschap_mppe_types], T_OP_EQ);
68     rad_assert(vp != NULL);
69     pairadd(avp, vp);
70
71     /* If no MPPE, we're done. */
72     if (!opt->mschap_mppe_policy)
73       return;
74
75     /*
76      * Generate the MS-CHAP-MPPE-Keys attribute.  This is not specified
77      * anywhere -- RFC 2548, par. 2.4.1 is the authority but it has
78      * typos and omissions that make this unimplementable.  The
79      * code here is based on experimental results provided by
80      * Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp>.
81      * We only support 128-bit keys derived from the NT hash; 40-bit
82      * and 56-bit keys are derived from the LM hash, which besides
83      * being deprecated, has severe security problems.
84      */
85     {
86       size_t i, passcode_len;
87       unsigned char password_unicode[2 * OTP_MAX_PASSCODE_LEN];
88       unsigned char password_md[MD4_DIGEST_LENGTH];
89       unsigned char mppe_keys[32];
90       /*                    0x    ASCII(mppe_keys)      '\0' */
91       char mppe_keys_string[2 + (2 * sizeof(mppe_keys)) + 1];
92
93       /* Zero the LM-Key sub-field (and padding). */
94       (void) memset(mppe_keys, 0, sizeof(mppe_keys));
95
96       /*
97        * The NT-Key sub-field is MD4(MD4(unicode(password))).
98        * Start by hashing the unicode passcode.
99        * This is broken because unicode chars are machine-ordered,
100        * but the spec (RFC 2433) doesn't say how to prepare
101        * the password for md4 (other than by example values).
102        */
103       passcode_len = strlen(passcode);
104       for (i = 0; i < passcode_len; ++i) {
105         /* Set the high order 8 bits to 0 (little-endian) */
106         password_unicode[i * 2] = *passcode++;
107         password_unicode[i * 2 + 1] = 0;
108       }
109       /* first md4 */
110       (void) MD4(password_unicode, 2 * passcode_len, password_md);
111       /* second md4 */
112       (void) MD4(password_md, MD4_DIGEST_LENGTH, &mppe_keys[8]);
113
114 #if 0 /* encoding now handled in lib/radius.c:rad_pwencode() */
115       {
116         unsigned char md5_md[MD5_DIGEST_LENGTH];
117         unsigned char encode_buf[AUTH_VECTOR_LEN + MAX_STRING_LEN];
118         int secretlen;
119
120         /* Now we must encode the key as User-Password is encoded. */
121         secretlen = strlen(request->secret);
122         (void) memcpy(encode_buf, request->secret, secretlen);
123         (void) memcpy(encode_buf + secretlen, request->packet->vector,
124                       AUTH_VECTOR_LEN);
125         (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN, md5_md);
126         for (i = 0; i < 16; ++i)
127           mppe_keys[i] ^= md5_md[i];
128         (void) memcpy(encode_buf + secretlen, mppe_keys, MD5_DIGEST_LENGTH);
129         (void) MD5(encode_buf, secretlen + MD5_DIGEST_LENGTH, md5_md);
130         for (i = 0; i < 16; ++i)
131           mppe_keys[i + 16] ^= md5_md[i];
132       }
133 #endif /* 0 */
134
135       /* Whew.  Now stringify it for pairmake(). */
136       mppe_keys_string[0] = '0';
137       mppe_keys_string[1] = 'x';
138       for (i = 0; i < 32; ++i)
139         (void) sprintf(&mppe_keys_string[i*2+2], "%02X", mppe_keys[i]);
140       vp = pairmake("MS-CHAP-MPPE-Keys", mppe_keys_string, T_OP_EQ);
141       rad_assert(vp != NULL);
142       pairadd(avp, vp);
143     } /* (doing mppe) */
144   break; /* PWE_MSCHAP */
145
146   case PWE_MSCHAP2:
147   {
148     size_t i;
149     unsigned char password_md_md[MD4_DIGEST_LENGTH];
150
151     /*
152      * MS-CHAPv2 requires mutual authentication; we must prove
153      * that we know the secret.  This is a bit circuitous: set
154      * MD1 = SHA(MD4(MD4(unicode(password)))|NT_RESPONSE|MAGIC1),
155      * MD2 = MSB8(SHA(PEER_CHALLENGE|MS_CHAP_CHALLENGE|USERNAME)),
156      * and finally use SHA(MD1|MD2|MAGIC2) as the authenticator.
157      * The authenticator is returned as the string "S=<auth>",
158      * <auth> is the authenticator expressed as [uppercase] ASCII.
159      * See RFC 2759.
160      */
161     {
162       size_t passcode_len;
163       unsigned char password_unicode[2 * OTP_MAX_PASSCODE_LEN];
164       unsigned char password_md[MD4_DIGEST_LENGTH];
165
166       SHA_CTX ctx;
167       unsigned char md1[SHA_DIGEST_LENGTH];
168       unsigned char md2[SHA_DIGEST_LENGTH];
169       unsigned char auth_md[SHA_DIGEST_LENGTH];
170       /*                  S=  (  ASCII(auth_md)   )  \0 */
171       char auth_md_string[2 + (2 * sizeof(auth_md)) + 1];
172       /*
173        * ugh.  The ASCII authenticator (auth_md_string) is sent
174        * along with a single (useless) binary byte (the ID).
175        * So we must "stringify" it again (for pairmake()) since the
176        * binary byte requires the attribute to be of type "octets".
177        */
178       /*                    0x  (ID) ( ASCII("S="ASCII(auth_md))) */
179       char auth_octet_string[2 + 2 + (2 * sizeof(auth_md_string))];
180
181       char *username = request->username->strvalue;
182       int username_len = request->username->length;
183
184       /* "Magic server to client signing constant" */
185       unsigned char magic1[39] =
186         { 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
187           0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
188           0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
189           0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74 };
190       /* "Pad to make it do more than one iteration" */
191       unsigned char magic2[41] =
192         { 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
193           0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
194           0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
195           0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
196           0x6E };
197
198       /*
199        * Start by hashing the unicode passcode.
200        * This is broken because unicode chars are machine-ordered,
201        * but the spec (RFC 2759) doesn't say how to prepare
202        * the password for md4 (other than by example values).
203        */
204       passcode_len = strlen(passcode);
205       for (i = 0; i < passcode_len; ++i) {
206         /* Set the high order 8 bits to 0 (little-endian) */
207         password_unicode[i * 2] = *passcode++;
208         password_unicode[i * 2 + 1] = 0;
209       }
210       /* first md4 */
211       (void) MD4(password_unicode, 2 * passcode_len, password_md);
212       /* second md4 */
213       (void) MD4(password_md, MD4_DIGEST_LENGTH, password_md_md);
214
215       /* MD1 */
216       SHA1_Init(&ctx);
217       SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
218       SHA1_Update(&ctx, rvp->strvalue + 26, 24);
219       SHA1_Update(&ctx, magic1, sizeof(magic1));
220       SHA1_Final(md1, &ctx);
221
222       /* MD2 */
223       SHA1_Init(&ctx);
224       SHA1_Update(&ctx, rvp->strvalue + 2, 16);
225       SHA1_Update(&ctx, cvp->strvalue, 16);
226       SHA1_Update(&ctx, username, username_len);
227       SHA1_Final(md2, &ctx);
228
229       /* The Authenticator */
230       SHA1_Init(&ctx);
231       SHA1_Update(&ctx, md1, SHA_DIGEST_LENGTH);
232       SHA1_Update(&ctx, md2, 8);
233       SHA1_Update(&ctx, magic2, sizeof(magic2));
234       SHA1_Final(auth_md, &ctx);
235
236       /* String conversion. */
237       auth_md_string[0] = 'S';
238       auth_md_string[1] = '=';
239       for (i = 0; i < sizeof(auth_md); ++i)
240         (void) sprintf(&auth_md_string[i * 2 + 2], "%02X", auth_md[i]);
241
242       /* And then octet conversion.  Ugh! */
243       auth_octet_string[0] = '0';
244       auth_octet_string[1] = 'x';
245       (void) sprintf(&auth_octet_string[2], "%02X", rvp->strvalue[0]);
246       for (i = 0; i < sizeof(auth_md_string) - 1; ++i)
247         (void) sprintf(&auth_octet_string[i * 2 +4], "%02X", auth_md_string[i]);
248
249       vp = pairmake("MS-CHAP2-Success", auth_octet_string, T_OP_EQ);
250       rad_assert(vp != NULL);
251       pairadd(avp, vp);
252     } /* Generate mutual auth info. */
253
254     /*
255      * Now, set some MPPE related attributes.
256      */
257     vp = pairmake("MS-MPPE-Encryption-Policy",
258                   otp_mppe_policy[opt->mschapv2_mppe_policy], T_OP_EQ);
259     rad_assert(vp != NULL);
260     pairadd(avp, vp);
261     vp = pairmake("MS-MPPE-Encryption-Types",
262                   otp_mppe_types[opt->mschapv2_mppe_types], T_OP_EQ);
263     rad_assert(vp != NULL);
264     pairadd(avp, vp);
265
266     /* If no MPPE, we're done. */
267     if (!opt->mschapv2_mppe_policy)
268       return;
269
270     /*
271      * Generate the MPPE initial session key, per RFC 3079.
272      * (Although, RFC 2548 leaves us guessing at how to generate this.)
273      * For MS-CHAPv2 we support all key lengths (40-, 56- and 128-bit),
274      * although MPPE via RADIUS supports only 40- and 128-bit keys.
275      * This is a bit more complicated than MS-CHAP.  Start by generating
276      * a "master session key"
277      *    MSB16(SHA(NTPasswordHashHash|NT_RESPONSE|MAGIC1)), where
278      * NTPasswordHashHash is MD4(MD4(unicode(password))), NT_RESPONSE
279      * is from the MS-CHAP2-Response attribute, and MAGIC1 is a
280      * constant from RFC 3079.  Then, we derive asymmetric send/receive
281      * keys from the master session key.  The "master send key" is
282      *     MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC3|SHSPAD2)),
283      * and the "master receive key" is
284      *     MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC2|SHSPAD2)), where
285      * MASTERKEY is the "master session key" generated above, and the
286      * other values are constants from RFC 3079.  MSBx is the x-most
287      * significant bytes, where x is 5, 7, or 16 as appropriate for
288      * the desired key length.  We always generate 16 byte (128-bit)
289      * keys, the NAS is required to truncate as needed.
290      */
291     {
292       /* These constants and key vars are named from RFC 3079. */
293       /* "This is the MPPE Master Key" */
294       unsigned char Magic1[27] =
295         { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
296           0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
297           0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
298       /* "On the client side, this is the send key; "
299          "on the server side, it is the receive key." */
300       unsigned char Magic2[84] =
301         { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
302           0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
303           0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
304           0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
305           0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
306           0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
307           0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
308           0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
309           0x6b, 0x65, 0x79, 0x2e };
310       /* "On the client side, this is the receive key; "
311          "on the server side, it is the send key." */
312       unsigned char Magic3[84] =
313         { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
314           0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
315           0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
316           0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
317           0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
318           0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
319           0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
320           0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
321           0x6b, 0x65, 0x79, 0x2e };
322       unsigned char SHSpad1[40] =
323         { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
324           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
325           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
326           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
327       unsigned char SHSpad2[40] =
328         { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
329           0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
330           0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
331           0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
332       unsigned char MasterKey[16];
333       unsigned char MasterSendKey[16];
334       unsigned char MasterReceiveKey[16];
335
336       SHA_CTX ctx;
337       unsigned char sha_md[SHA_DIGEST_LENGTH];
338 #if 0 /* salting/encoding now handled in lib/radius.c:tunnel_pwencode() */
339       unsigned char md5_md[MD5_DIGEST_LENGTH];
340
341       /*   From RFC 2548:           S                 R           A */
342       unsigned char encode_buf[MAX_STRING_LEN + AUTH_VECTOR_LEN + 2];
343       int secretlen;
344
345       /* A useless value required by RFC 2548. */
346       unsigned char salt[2];
347       unsigned char mppe_key[32]; /* 1 + 16 + padding */
348       /*                           0x   (   ASCII(salt)  ) */
349       unsigned char mppe_key_string[2 + (2 * sizeof(salt)) +
350       /*                            (   ASCII(mppe_key)  )  \0 */
351                                     (2 * sizeof(mppe_key)) + 1];
352 #else /* 0 */
353       /*                           0x   (   ASCII(mppe_key)   )  \0 */
354       unsigned char mppe_key_string[2 + (2 * sizeof(MasterKey)) + 1];
355 #endif /* else !0 */
356
357       /* Generate the master session key. */
358       SHA1_Init(&ctx);
359       SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
360       SHA1_Update(&ctx, rvp->strvalue + 26, 24);
361       SHA1_Update(&ctx, Magic1, sizeof(Magic1));
362       SHA1_Final(sha_md, &ctx);
363       (void) memcpy(MasterKey, sha_md, 16);
364
365       /* Generate the master send key. */
366       SHA1_Init(&ctx);
367       SHA1_Update(&ctx, MasterKey, 16);
368       SHA1_Update(&ctx, SHSpad1, 40);
369       SHA1_Update(&ctx, Magic3, sizeof(Magic3));
370       SHA1_Update(&ctx, SHSpad2, 40);
371       SHA1_Final(sha_md, &ctx);
372       (void) memcpy(MasterSendKey, sha_md, 16);
373
374       /* Generate the master receive key. */
375       SHA1_Init(&ctx);
376       SHA1_Update(&ctx, MasterKey, 16);
377       SHA1_Update(&ctx, SHSpad1, 40);
378       SHA1_Update(&ctx, Magic2, sizeof(Magic3));
379       SHA1_Update(&ctx, SHSpad2, 40);
380       SHA1_Final(sha_md, &ctx);
381       (void) memcpy(MasterReceiveKey, sha_md, 16);
382
383       /*
384        * Now, generate the MS-MPPE-Send-Key attribute.
385        */
386 #if 0
387       /* Setup the salt value. */
388       salt[0] = 0x80;
389       salt[1] = 0x01;
390
391       /* Encode the key. */
392       (void) memset(mppe_key, 0, sizeof(mppe_key));
393       mppe_key[0] = 16; /* length */
394       (void) memcpy(&mppe_key[1], MasterSendKey, 16);
395       secretlen = strlen(request->secret);
396       (void) memcpy(encode_buf, request->secret, secretlen);
397       (void) memcpy(encode_buf + secretlen, request->packet->vector,
398                     AUTH_VECTOR_LEN);
399       (void) memcpy(encode_buf + secretlen + 16, salt, 2);
400       (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
401       for (i = 0; i < 16; ++i)
402         mppe_key[i] ^= md5_md[i];
403       (void) memcpy(encode_buf + secretlen, mppe_key, 16);
404       (void) MD5(encode_buf, secretlen + 16, md5_md);
405       for (i = 0; i < 16; ++i)
406         mppe_key[i + 16] ^= md5_md[i];
407
408       /* Whew.  Now stringify it for pairmake(). */
409       mppe_key_string[0] = '0';
410       mppe_key_string[1] = 'x';
411       (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
412       (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
413       for (i = 0; i < sizeof(mppe_key); ++i)
414         (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
415 #else /* 0 */
416       mppe_key_string[0] = '0';
417       mppe_key_string[1] = 'x';
418       for (i = 0; i < sizeof(MasterSendKey); ++i)
419         (void) sprintf(&mppe_key_string[i*2+2], "%02X", MasterSendKey[i]);
420 #endif /* else !0 */
421       vp = pairmake("MS-MPPE-Send-Key", mppe_key_string, T_OP_EQ);
422       rad_assert(vp != NULL);
423       pairadd(avp, vp);
424
425       /*
426        * Generate the MS-MPPE-Recv-Key attribute.
427        */
428 #if 0
429       /* Setup the salt value. */
430       salt[0] = 0x80;
431       salt[1] = 0x02;
432
433       /* Encode the key. */
434       (void) memset(mppe_key, 0, sizeof(mppe_key));
435       mppe_key[0] = 16; /* length */
436       (void) memcpy(&mppe_key[1], MasterReceiveKey, 16);
437       secretlen = strlen(request->secret);
438       (void) memcpy(encode_buf, request->secret, secretlen);
439       (void) memcpy(encode_buf + secretlen, request->packet->vector,
440                     AUTH_VECTOR_LEN);
441       (void) memcpy(encode_buf + secretlen + 16, salt, 2);
442       (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
443       for (i = 0; i < 16; ++i)
444         mppe_key[i] ^= md5_md[i];
445       (void) memcpy(encode_buf + secretlen, mppe_key, 16);
446       (void) MD5(encode_buf, secretlen + 16, md5_md);
447       for (i = 0; i < 16; ++i)
448         mppe_key[i + 16] ^= md5_md[i];
449
450       /* Whew.  Now stringify it for pairmake(). */
451       mppe_key_string[0] = '0';
452       mppe_key_string[1] = 'x';
453       (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
454       (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
455       for (i = 0; i < sizeof(mppe_key); ++i)
456         (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
457 #else /* 0 */
458       mppe_key_string[0] = '0';
459       mppe_key_string[1] = 'x';
460       for (i = 0; i < sizeof(MasterReceiveKey); ++i)
461         (void) sprintf(&mppe_key_string[i*2+2], "%02X", MasterReceiveKey[i]);
462 #endif /* else !0 */
463       vp = pairmake("MS-MPPE-Recv-Key", mppe_key_string, T_OP_EQ);
464       rad_assert(vp != NULL);
465       pairadd(avp, vp);
466
467     } /* (doing mppe) */
468   } /* PWE_MSCHAP2 */
469   break; /* PWE_MSCHAP2 */
470
471   } /* switch (pwe) */
472
473   return;
474 }