1 /* 2 // Copyright (C) 2002 Microsoft Corporation 3 // All rights reserved. 4 // 5 // THIS CODE AND INFORMATION IS PROVIDED "AS IS" 6 // WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 7 // OR IMPLIED, INCLUDING BUT NOT LIMITED 8 // TO THE IMPLIED WARRANTIES OF MERCHANTIBILITY 9 // AND/OR FITNESS FOR A PARTICULAR PURPOSE. 10 // 11 // Date - 10/08/2002 12 // Author - Sanj Surati 13 14 15 ///////////////////////////////////////////////////////////// 16 // 17 // DERPARSE.C 18 // 19 // SPNEGO Token Handler Source File 20 // 21 // Contains implementation of ASN.1 DER read/write functions 22 // as defined in DERPARSE.H. 23 // 24 ///////////////////////////////////////////////////////////// 25 26 */ 27 28 #pragma ident "%Z%%M% %I% %E% SMI" 29 30 #include <stdlib.h> 31 #include <stdio.h> 32 #include <memory.h> 33 #include <sys/byteorder.h> 34 #include "spnego.h" 35 #include "derparse.h" 36 37 /* 38 // 39 // The GSS Mechanism OID enumeration values (SPNEGO_MECH_OID) control which offset in 40 // the array below, that a mechanism can be found. 41 // 42 */ 43 #pragma error_messages (off,E_INITIALIZATION_TYPE_MISMATCH) 44 MECH_OID g_stcMechOIDList [] = 45 { 46 {"\x06\x09\x2a\x86\x48\x82\xf7\x12\x01\x02\x02", 11, 9, 47 spnego_mech_oid_Kerberos_V5_Legacy }, // 1.2.840.48018.1.2.2 48 {"\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02", 11, 9, 49 spnego_mech_oid_Kerberos_V5 }, // 1.2.840.113554.1.2.2 50 {"\x06\x06\x2b\x06\x01\x05\x05\x02", 8, 6, 51 spnego_mech_oid_Spnego }, // 1.3.6.1.5.5.2 52 {"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a", 12, 10, 53 spnego_mech_oid_NTLMSSP }, // 1.3.6.1.4.1.311.2.2.10 54 {"", 0, 0, spnego_mech_oid_NotUsed } // Placeholder 55 }; 56 #pragma error_messages (default,E_INITIALIZATION_TYPE_MISMATCH) 57 58 /* 59 ///////////////////////////////////////////////////////////////////////////// 60 // 61 // Function: 62 // ASNDerGetLength 63 // 64 // Parameters: 65 // [in] pbLengthData - DER Length Data 66 // [in] nBoundaryLength - Length that value must not exceed. 67 // [out] pnLength - Filled out with length value 68 // [out] pnNumLengthBytes - Filled out with number of bytes 69 // consumed by DER length. 70 // 71 // Returns: 72 // int Success - SPNEGO_E_SUCCESS 73 // Failure - SPNEGO API Error code 74 // 75 // Comments : 76 // Interprets the data at pbLengthData as a DER length. The length must 77 // fit within the bounds of nBoundary length. We do not currently 78 // process lengths that take more than 4 bytes. 79 // 80 //////////////////////////////////////////////////////////////////////////// 81 */ 82 83 int ASNDerGetLength( unsigned char* pbLengthData, long nBoundaryLength, long* pnLength, 84 long* pnNumLengthBytes ) 85 { 86 int nReturn = SPNEGO_E_INVALID_LENGTH; 87 int nNumLengthBytes = 0; 88 89 // First check if the extended length bit is set 90 91 if ( *pbLengthData & LEN_XTND ) 92 { 93 // Lower 7 bits contain the number of trailing bytes that describe the length 94 nNumLengthBytes = *pbLengthData & LEN_MASK; 95 96 // Check that the number of bytes we are about to read is within our boundary 97 // constraints 98 99 if ( nNumLengthBytes <= nBoundaryLength - 1 ) 100 { 101 102 // For now, our handler won't deal with lengths greater than 4 bytes 103 if ( nNumLengthBytes >= 1 && nNumLengthBytes <= 4 ) 104 { 105 // 0 out the initial length 106 *pnLength = 0L; 107 108 // Bump by 1 byte 109 pbLengthData++; 110 111 #ifdef _LITTLE_ENDIAN 112 113 // There may be a cleaner way to do this, but for now, this seems to be 114 // an easy way to do the transformation 115 switch ( nNumLengthBytes ) 116 { 117 case 1: 118 { 119 *( ( (unsigned char*) pnLength ) ) = *pbLengthData; 120 break; 121 } 122 123 case 2: 124 { 125 *( ( (unsigned char*) pnLength ) ) = *(pbLengthData + 1); 126 *( ( (unsigned char*) pnLength ) + 1 ) = *(pbLengthData); 127 128 break; 129 } 130 131 case 3: 132 { 133 *( ( (unsigned char*) pnLength ) ) = *(pbLengthData + 2); 134 *( ( (unsigned char*) pnLength ) + 2 ) = *(pbLengthData + 1); 135 *( ( (unsigned char*) pnLength ) + 3 ) = *(pbLengthData); 136 break; 137 } 138 139 case 4: 140 { 141 *( ( (unsigned char*) pnLength ) ) = *(pbLengthData + 3); 142 *( ( (unsigned char*) pnLength ) + 1 ) = *(pbLengthData + 2); 143 *( ( (unsigned char*) pnLength ) + 2 ) = *(pbLengthData + 1); 144 *( ( (unsigned char*) pnLength ) + 3 ) = *(pbLengthData); 145 break; 146 } 147 148 } // SWITCH ( nNumLengthBytes ) 149 150 #else 151 // We are Big-Endian, so the length can be copied in from the source 152 // as is. Ensure that we adjust for the number of bytes we actually 153 // copy. 154 155 memcpy( ( (unsigned char *) pnLength ) + ( 4 - nNumLengthBytes ), 156 pbLengthData, nNumLengthBytes ); 157 #endif 158 159 // Account for the initial length byte 160 *pnNumLengthBytes = nNumLengthBytes + 1; 161 nReturn = SPNEGO_E_SUCCESS; 162 163 } // IF Valid Length 164 165 } // IF num bytes to read is within the boundary length 166 167 } // IF xtended length 168 else 169 { 170 171 // Extended bit is not set, so the length is in the value and the one 172 // byte describes the length 173 *pnLength = *pbLengthData & LEN_MASK; 174 *pnNumLengthBytes = 1; 175 nReturn = SPNEGO_E_SUCCESS; 176 177 } 178 179 return nReturn; 180 } 181 182 183 /* 184 ///////////////////////////////////////////////////////////////////////////// 185 // 186 // Function: 187 // ASNDerCheckToken 188 // 189 // Parameters: 190 // [in] pbTokenData - Token Data 191 // [in] nToken - Token identifier to check for 192 // [in] nLengthWithToken - Expected token length (with data) 193 // [in] nBoundaryLength - Length that value must not exceed. 194 // [out] pnLength - Filled out with data length 195 // [out] pnTokenLength - Filled out with number of bytes 196 // consumed by token identifier and length. 197 // 198 // Returns: 199 // int Success - SPNEGO_E_SUCCESS 200 // Failure - SPNEGO API Error code 201 // 202 // Comments : 203 // Checks the data pointed to by pbTokenData for the specified token 204 // identifier and the length that immediately follows. If 205 // nLengthWithToken is > 0, the calculated length must match. The 206 // length must also not exceed the specified boundary length . 207 // 208 //////////////////////////////////////////////////////////////////////////// 209 */ 210 211 int ASNDerCheckToken( unsigned char* pbTokenData, unsigned char nToken, 212 long nLengthWithToken, long nBoundaryLength, 213 long* pnLength, long* pnTokenLength ) 214 { 215 216 int nReturn = SPNEGO_E_INVALID_LENGTH; 217 long nNumLengthBytes = 0L; 218 219 // Make sure that we've at least got 2 bytes of room to work with 220 221 if ( nBoundaryLength >= 2 ) 222 { 223 // The first byte of the token data MUST match the specified token 224 if ( *pbTokenData == nToken ) 225 { 226 // Next byte indicates the length 227 pbTokenData++; 228 229 // Get the length described by the token 230 if ( ( nReturn = ASNDerGetLength( pbTokenData, nBoundaryLength, pnLength, 231 &nNumLengthBytes ) ) == SPNEGO_E_SUCCESS ) 232 { 233 // Verify that the length is LESS THAN the boundary length 234 // (this should prevent us walking out of our buffer) 235 if ( ( nBoundaryLength - ( nNumLengthBytes + 1 ) < *pnLength ) ) 236 { 237 238 nReturn = SPNEGO_E_INVALID_LENGTH; 239 240 } 241 242 // If we were passed a length to check, do so now 243 if ( nLengthWithToken > 0L ) 244 { 245 246 // Check that the expected length matches 247 if ( ( nLengthWithToken - ( nNumLengthBytes + 1 ) ) != *pnLength ) 248 { 249 250 nReturn = SPNEGO_E_INVALID_LENGTH; 251 252 } 253 254 } // IF need to validate length 255 256 if ( SPNEGO_E_SUCCESS == nReturn ) 257 { 258 *pnTokenLength = nNumLengthBytes + 1; 259 } 260 261 } // IF ASNDerGetLength 262 263 } // IF token matches 264 else 265 { 266 nReturn = SPNEGO_E_TOKEN_NOT_FOUND; 267 } 268 269 } // IF Boundary Length is at least 2 bytes 270 271 return nReturn; 272 } 273 274 /* 275 ///////////////////////////////////////////////////////////////////////////// 276 // 277 // Function: 278 // ASNDerCheckOID 279 // 280 // Parameters: 281 // [in] pbTokenData - Token Data 282 // [in] nMechOID - OID we are looking for 283 // [in] nBoundaryLength - Length that value must not exceed. 284 // [out] pnTokenLength - Filled out with number of bytes 285 // consumed by token and data. 286 // 287 // Returns: 288 // int Success - SPNEGO_E_SUCCESS 289 // Failure - SPNEGO API Error code 290 // 291 // Comments : 292 // Checks the data pointed to by pbTokenData for the specified OID. 293 // 294 //////////////////////////////////////////////////////////////////////////// 295 */ 296 297 int ASNDerCheckOID( unsigned char* pbTokenData, SPNEGO_MECH_OID nMechOID, long nBoundaryLength, 298 long* pnTokenLength ) 299 { 300 int nReturn = 0L; 301 long nLength = 0L; 302 303 // Verify that we have an OID token 304 if ( ( nReturn = ASNDerCheckToken( pbTokenData, OID, 0L, nBoundaryLength, 305 &nLength, pnTokenLength ) ) == SPNEGO_E_SUCCESS ) 306 { 307 // Add the data length to the Token Length 308 *pnTokenLength += nLength; 309 310 // Token Lengths plus the actual length must match the length in our OID list element. 311 // If it doesn't, we're done 312 if ( *pnTokenLength == g_stcMechOIDList[nMechOID].iLen ) 313 { 314 // Memcompare the token and the expected field 315 if ( memcmp( pbTokenData, g_stcMechOIDList[nMechOID].ucOid, *pnTokenLength ) != 0 ) 316 { 317 nReturn = SPNEGO_E_UNEXPECTED_OID; 318 } 319 } 320 else 321 { 322 nReturn = SPNEGO_E_UNEXPECTED_OID; 323 } 324 325 } // IF OID Token CHecks 326 327 return nReturn; 328 } 329 330 /* 331 ///////////////////////////////////////////////////////////////////////////// 332 // 333 // Function: 334 // ASNDerCalcNumLengthBytes 335 // 336 // Parameters: 337 // [in] nLength - Length to calculate length bytes for. 338 // 339 // Returns: 340 // int Number of bytes necessary to represent length 341 // 342 // Comments : 343 // Helper function to calculate the number of length bytes necessary to 344 // represent a length value. For our purposes, a 32-bit value should be 345 // enough to describea length. 346 // 347 //////////////////////////////////////////////////////////////////////////// 348 */ 349 350 int ASNDerCalcNumLengthBytes( long nLength ) 351 { 352 if ( nLength <= 0x7F ) 353 { 354 // A single byte will be sufficient for describing this length. 355 // The byte will simply contain the length 356 return 1; 357 } 358 else if ( nLength <= 0xFF ) 359 { 360 // Two bytes are necessary, one to say how many following bytes 361 // describe the length, and one to give the length 362 return 2; 363 } 364 else if ( nLength <= 0xFFFF ) 365 { 366 // Three bytes are necessary, one to say how many following bytes 367 // describe the length, and two to give the length 368 return 3; 369 } 370 else if ( nLength <= 0xFFFFFF ) 371 { 372 // Four bytes are necessary, one to say how many following bytes 373 // describe the length, and three to give the length 374 return 4; 375 } 376 else 377 { 378 // Five bytes are necessary, one to say how many following bytes 379 // describe the length, and four to give the length 380 return 5; 381 } 382 } 383 384 385 ///////////////////////////////////////////////////////////////////////////// 386 // 387 // Function: 388 // ASNDerCalcTokenLength 389 // 390 // Parameters: 391 // [in] nLength - Length to calculate length bytes for. 392 // [in] nDataLength - Actual Data length value. 393 // 394 // Returns: 395 // long Number of bytes necessary to represent a token, length and data 396 // 397 // Comments : 398 // Helper function to calculate a token and value size, based on a 399 // supplied length value, and any binary data that will need to be 400 // written out. 401 // 402 //////////////////////////////////////////////////////////////////////////// 403 404 long ASNDerCalcTokenLength( long nLength, long nDataLength ) 405 { 406 // Add a byte to the length size to account for a single byte to 407 // hold the token type. 408 long nTotalLength = ASNDerCalcNumLengthBytes( nLength ) + 1; 409 410 return nTotalLength + nDataLength; 411 } 412 413 414 ///////////////////////////////////////////////////////////////////////////// 415 // 416 // Function: 417 // ASNDerCalcElementLength 418 // 419 // Parameters: 420 // [in] nDataLength - Length of data. 421 // [out] pnInternalLength - Filled out with length of element 422 // without sequence info. 423 // 424 // Returns: 425 // long Number of bytes necessary to represent an element 426 // 427 // Comments : 428 // Helper function to calculate an element length. An element consists 429 // of a sequence token, a type token and then the data. 430 // 431 //////////////////////////////////////////////////////////////////////////// 432 433 long ASNDerCalcElementLength( long nDataLength, long* pnInternalLength ) 434 { 435 // First the type token and the actual data 436 long nTotalLength = ASNDerCalcTokenLength( nDataLength, nDataLength ); 437 438 // Internal length is the length without the element sequence token 439 if ( NULL != pnInternalLength ) 440 { 441 *pnInternalLength = nTotalLength; 442 } 443 444 // Next add in the element's sequence token (remember that its 445 // length is the total length of the type token and data) 446 nTotalLength += ASNDerCalcTokenLength( nTotalLength, 0L ); 447 448 return nTotalLength; 449 } 450 451 ///////////////////////////////////////////////////////////////////////////// 452 // 453 // Function: 454 // ASNDerCalcMechListLength 455 // 456 // Parameters: 457 // [in] mechoid - Mech OID to put in list. 458 // [out] pnInternalLength - Filled out with length of element 459 // without the primary sequence token. 460 // 461 // Returns: 462 // long Number of bytes necessary to represent a mechList 463 // 464 // Comments : 465 // Helper function to calculate a MechList length. A mechlist consists 466 // of a NegTokenInit sequence token, a sequence token for the MechList 467 // and finally a list of OIDs. In our case, we only really have one 468 // OID. 469 // 470 //////////////////////////////////////////////////////////////////////////// 471 472 long ASNDerCalcMechListLength( SPNEGO_MECH_OID mechoid, long* pnInternalLength ) 473 { 474 // First the OID 475 long nTotalLength = g_stcMechOIDList[mechoid].iLen; 476 477 // Next add in a sequence token 478 nTotalLength += ASNDerCalcTokenLength( nTotalLength, 0L ); 479 480 // Internal length is the length without the element sequence token 481 if ( NULL != pnInternalLength ) 482 { 483 *pnInternalLength = nTotalLength; 484 } 485 486 // Finally add in the element's sequence token 487 nTotalLength += ASNDerCalcTokenLength( nTotalLength, 0L ); 488 489 return nTotalLength; 490 } 491 492 493 ///////////////////////////////////////////////////////////////////////////// 494 // 495 // Function: 496 // ASNDerWriteLength 497 // 498 // Parameters: 499 // [out] pbData - Buffer to write into. 500 // [in] nLength - Length to write out. 501 // 502 // Returns: 503 // int Number of bytes written out 504 // 505 // Comments : 506 // Helper function to write out a length value following DER rules . 507 // 508 //////////////////////////////////////////////////////////////////////////// 509 510 int ASNDerWriteLength( unsigned char* pbData, long nLength ) 511 { 512 int nNumBytesRequired = ASNDerCalcNumLengthBytes( nLength ); 513 int nNumLengthBytes = nNumBytesRequired - 1; 514 515 516 if ( nNumBytesRequired > 1 ) 517 { 518 519 // Write out the number of bytes following which will be used 520 *pbData = (unsigned char ) ( LEN_XTND | nNumLengthBytes ); 521 522 // Point to where we'll actually write the length 523 pbData++; 524 525 #ifdef _LITTLE_ENDIAN 526 527 // There may be a cleaner way to do this, but for now, this seems to be 528 // an easy way to do the transformation 529 switch ( nNumLengthBytes ) 530 { 531 case 1: 532 { 533 // Cast the length to a single byte, since we know that it 534 // is 0x7F or less (or we wouldn't only need a single byte). 535 536 *pbData = (unsigned char) nLength; 537 break; 538 } 539 540 case 2: 541 { 542 *pbData = *( ( (unsigned char*) &nLength ) + 1 ); 543 *( pbData + 1) = *( ( (unsigned char*) &nLength ) ); 544 break; 545 } 546 547 case 3: 548 { 549 *pbData = *( ( (unsigned char*) &nLength ) + 3 ); 550 *( pbData + 1) = *( ( (unsigned char*) &nLength ) + 2 ); 551 *( pbData + 2) = *( ( (unsigned char*) &nLength ) ); 552 break; 553 } 554 555 case 4: 556 { 557 *pbData = *( ( (unsigned char*) &nLength ) + 3 ); 558 *( pbData + 1) = *( ( (unsigned char*) &nLength ) + 2 ); 559 *( pbData + 2) = *( ( (unsigned char*) &nLength ) + 1 ); 560 *( pbData + 3) = *( ( (unsigned char*) &nLength ) ); 561 break; 562 } 563 564 } // SWITCH ( nNumLengthBytes ) 565 566 #else 567 // We are Big-Endian, so the length can be copied in from the source 568 // as is. Ensure that we adjust for the number of bytes we actually 569 // copy. 570 571 memcpy( pbData, 572 ( (unsigned char *) &nLength ) + ( 4 - nNumLengthBytes ), nNumLengthBytes ); 573 #endif 574 575 } // IF > 1 byte for length 576 else 577 { 578 // Cast the length to a single byte, since we know that it 579 // is 0x7F or less (or we wouldn't only need a single byte). 580 581 *pbData = (unsigned char) nLength; 582 } 583 584 return nNumBytesRequired; 585 } 586 587 ///////////////////////////////////////////////////////////////////////////// 588 // 589 // Function: 590 // ASNDerWriteToken 591 // 592 // Parameters: 593 // [out] pbData - Buffer to write into. 594 // [in] ucType - Token Type 595 // [in] pbTokenValue - Actual Value 596 // [in] nLength - Length of Data. 597 // 598 // Returns: 599 // int Number of bytes written out 600 // 601 // Comments : 602 // Helper function to write out a token and any associated data. If 603 // pbTokenValue is non-NULL, then it is written out in addition to the 604 // token identifier and the length bytes. 605 // 606 //////////////////////////////////////////////////////////////////////////// 607 608 int ASNDerWriteToken( unsigned char* pbData, unsigned char ucType, 609 unsigned char* pbTokenValue, long nLength ) 610 { 611 int nTotalBytesWrittenOut = 0L; 612 int nNumLengthBytesWritten = 0L; 613 614 // Write out the type 615 *pbData = ucType; 616 617 // Wrote 1 byte, and move data pointer 618 nTotalBytesWrittenOut++; 619 pbData++; 620 621 // Now write out the length and adjust the number of bytes written out 622 nNumLengthBytesWritten = ASNDerWriteLength( pbData, nLength ); 623 624 nTotalBytesWrittenOut += nNumLengthBytesWritten; 625 pbData += nNumLengthBytesWritten; 626 627 // Write out the token value if we got one. The assumption is that the 628 // nLength value indicates how many bytes are in pbTokenValue. 629 630 if ( NULL != pbTokenValue ) 631 { 632 memcpy( pbData, pbTokenValue, nLength ); 633 nTotalBytesWrittenOut += nLength; 634 } 635 636 return nTotalBytesWrittenOut; 637 } 638 639 640 ///////////////////////////////////////////////////////////////////////////// 641 // 642 // Function: 643 // ASNDerWriteOID 644 // 645 // Parameters: 646 // [out] pbData - Buffer to write into. 647 // [in] eMechOID - OID to write out. 648 // 649 // Returns: 650 // int Number of bytes written out 651 // 652 // Comments : 653 // Helper function to write out an OID. For these we have the raw bytes 654 // listed in a global structure. The caller simply indicates which OID 655 // should be written and we will splat out the data. 656 // 657 //////////////////////////////////////////////////////////////////////////// 658 659 int ASNDerWriteOID( unsigned char* pbData, SPNEGO_MECH_OID eMechOID ) 660 { 661 662 memcpy( pbData, g_stcMechOIDList[eMechOID].ucOid, g_stcMechOIDList[eMechOID].iLen ); 663 664 return g_stcMechOIDList[eMechOID].iLen; 665 } 666 667 668 ///////////////////////////////////////////////////////////////////////////// 669 // 670 // Function: 671 // ASNDerWriteMechList 672 // 673 // Parameters: 674 // [out] pbData - Buffer to write into. 675 // [in] eMechOID - OID to put in MechList. 676 // 677 // Returns: 678 // int Number of bytes written out 679 // 680 // Comments : 681 // Helper function to write out a MechList. A MechList consists of the 682 // Init Token Sequence, a sequence token and then the list of OIDs. In 683 // our case the OID is from a global array of known OIDs. 684 // 685 //////////////////////////////////////////////////////////////////////////// 686 687 long ASNDerWriteMechList( unsigned char* pbData, SPNEGO_MECH_OID mechoid ) 688 { 689 // First get the length 690 long nInternalLength = 0L; 691 long nMechListLength = ASNDerCalcMechListLength( mechoid, &nInternalLength ); 692 long nTempLength = 0L; 693 694 nTempLength = ASNDerWriteToken( pbData, SPNEGO_NEGINIT_ELEMENT_MECHTYPES, 695 NULL, nInternalLength ); 696 697 // Adjust the data pointer 698 pbData += nTempLength; 699 700 // Now write the Sequence token and the OID (the OID is a BLOB in the global 701 // structure. 702 703 nTempLength = ASNDerWriteToken( pbData, SPNEGO_CONSTRUCTED_SEQUENCE, 704 g_stcMechOIDList[mechoid].ucOid, 705 g_stcMechOIDList[mechoid].iLen ); 706 707 return nMechListLength; 708 } 709 710 711 ///////////////////////////////////////////////////////////////////////////// 712 // 713 // Function: 714 // ASNDerWriteElement 715 // 716 // Parameters: 717 // [out] pbData - Buffer to write into. 718 // [in] ucElementSequence - Sequence Token 719 // [in] ucType - Token Type 720 // [in] pbTokenValue - Actual Value 721 // [in] nLength - Length of Data. 722 // 723 // Returns: 724 // int Number of bytes written out 725 // 726 // Comments : 727 // Helper function to write out a SPNEGO Token element. An element 728 // consists of a sequence token, a type token and the associated data. 729 // 730 //////////////////////////////////////////////////////////////////////////// 731 732 int ASNDerWriteElement( unsigned char* pbData, unsigned char ucElementSequence, 733 unsigned char ucType, unsigned char* pbTokenValue, long nLength ) 734 { 735 // First get the length 736 long nInternalLength = 0L; 737 long nElementLength = ASNDerCalcElementLength( nLength, &nInternalLength ); 738 long nTempLength = 0L; 739 740 // Write out the sequence byte and the length of the type and data 741 nTempLength = ASNDerWriteToken( pbData, ucElementSequence, NULL, nInternalLength ); 742 743 // Adjust the data pointer 744 pbData += nTempLength; 745 746 // Now write the type and the data. 747 nTempLength = ASNDerWriteToken( pbData, ucType, pbTokenValue, nLength ); 748 749 return nElementLength; 750 } 751