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