1 16 package com.sun.slamd.example; 17 18 19 20 import java.io.*; 21 import java.net.*; 22 import java.security.*; 23 import java.util.*; 24 import netscape.ldap.*; 25 import com.sun.slamd.asn1.*; 26 import com.sun.slamd.common.*; 27 28 29 30 81 public class LDAPDigestMD5SocketFactory 82 implements LDAPSocketFactory 83 { 84 87 public static final char[] CNONCE_ALPHABET = 88 ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + 89 "1234567890+/").toCharArray(); 90 91 92 93 96 public static final String JCE_DIGEST_ALGORITHM = "MD5"; 97 98 99 100 103 public static final byte LDAP_BIND_REQUEST_TYPE = 0x60; 104 105 106 107 110 public static final byte LDAP_BIND_RESPONSE_TYPE = 0x61; 111 112 113 114 117 public static final byte LDAP_SASL_CREDENTIALS_TYPE = (byte) 0xA3; 118 119 120 121 125 public static final byte LDAP_SERVER_SASL_CREDENTIALS_TYPE = (byte) 0x87; 126 127 128 129 133 public static final String QOP_AUTH = "auth"; 134 135 136 137 141 public static final String SASL_MECHANISM_NAME = "DIGEST-MD5"; 142 143 144 145 LDAPSocketFactory socketFactory; 148 149 MessageDigest md5Digest; 151 152 SecureRandom random; 154 155 String authID; 158 159 String password; 162 163 164 165 174 public LDAPDigestMD5SocketFactory() 175 throws SLAMDException 176 { 177 try 178 { 179 md5Digest = MessageDigest.getInstance(JCE_DIGEST_ALGORITHM); 180 } 181 catch (Exception e) 182 { 183 throw new SLAMDException("Unable to initialize the MD5 digestor: " + e, 184 e); 185 } 186 187 random = new SecureRandom(); 188 189 authID = null; 190 password = null; 191 socketFactory = null; 192 } 193 194 195 196 203 public void setAuthenticationInfo(String authID, String password) 204 { 205 this.authID = authID; 206 this.password = password; 207 } 208 209 210 211 221 public void setAdditionalSocketFactory(LDAPSocketFactory socketFactory) 222 { 223 this.socketFactory = socketFactory; 224 } 225 226 227 228 242 public Socket makeSocket(String host, int port) 243 throws LDAPException 244 { 245 if ((authID == null) || (password == null)) 246 { 247 throw new LDAPException("Authentication ID and/or password has not been" + 248 "specified.", LDAPException.PARAM_ERROR); 249 } 250 251 252 Socket socket; 253 if (socketFactory == null) 254 { 255 try 256 { 257 socket = new Socket(host, port); 258 } 259 catch (IOException ioe) 260 { 261 throw new LDAPException("Unable to connect to " + host + ":" + port + 262 " -- " + ioe, LDAPException.CONNECT_ERROR); 263 } 264 } 265 else 266 { 267 socket = socketFactory.makeSocket(host, port); 268 } 269 270 271 InputStream inputStream; 274 OutputStream outputStream; 275 ASN1Reader asn1Reader; 276 ASN1Writer asn1Writer; 277 try 278 { 279 inputStream = socket.getInputStream(); 280 outputStream = socket.getOutputStream(); 281 asn1Reader = new ASN1Reader(inputStream); 282 asn1Writer = new ASN1Writer(outputStream); 283 } 284 catch (IOException ioe) 285 { 286 throw new LDAPException("Unable to get input and/or output stream -- " + 287 ioe, LDAPException.CONNECT_ERROR); 288 } 289 290 291 try 293 { 294 doBind(asn1Reader, asn1Writer, host, authID, password); 295 } 296 catch (LDAPException le) 297 { 298 throw le; 299 } 300 catch (Exception e) 301 { 302 throw new LDAPException("Internal failure while processing the bind: " + 303 e); 304 } 305 306 307 return socket; 309 } 310 311 312 313 327 private void doBind(ASN1Reader asn1Reader, ASN1Writer asn1Writer, String host, 328 String authID, String password) 329 throws LDAPException 330 { 331 ASN1Element[] saslCredentialElements = 333 { 334 new ASN1OctetString(SASL_MECHANISM_NAME), 335 new ASN1OctetString() 336 }; 337 338 ASN1Element[] bindRequestElements = 339 { 340 new ASN1Integer(3), 341 new ASN1OctetString(), 342 new ASN1Sequence(LDAP_SASL_CREDENTIALS_TYPE, saslCredentialElements) 343 }; 344 345 ASN1Element[] ldapMessageElements = 346 { 347 new ASN1Integer(1), 348 new ASN1Sequence(LDAP_BIND_REQUEST_TYPE, bindRequestElements) 349 }; 350 351 ASN1Element messageElement = new ASN1Sequence(ldapMessageElements); 352 353 354 try 356 { 357 asn1Writer.writeElement(messageElement); 358 } 359 catch (IOException ioe) 360 { 361 throw new LDAPException("Unable to send the initial bind request to " + 362 "the server: " + ioe, 363 LDAPException.CONNECT_ERROR); 364 } 365 366 367 ASN1Element responseElement; 369 try 370 { 371 responseElement = 372 asn1Reader.readElement(Constants.MAX_BLOCKING_READ_TIME); 373 } 374 catch (ASN1Exception ae) 375 { 376 throw new LDAPException("Unable to decode the initial bind response " + 377 "from the server: " + ae, 378 LDAPException.UNAVAILABLE); 379 } 380 catch (IOException ioe) 381 { 382 throw new LDAPException("Unable to read the initial bind response " + 383 "from the server: " + ioe, 384 LDAPException.CONNECT_ERROR); 385 } 386 387 388 String responseData = null; 390 try 391 { 392 ASN1Element[] elements = responseElement.decodeAsSequence().getElements(); 393 if (elements.length != 2) 394 { 395 throw new LDAPException("Unable to decode the initial bind response " + 396 "from the server: response element had an " + 397 "invalid number of elements.", 398 LDAPException.UNAVAILABLE); 399 } 400 401 if (elements[1].getType() != LDAP_BIND_RESPONSE_TYPE) 402 { 403 throw new LDAPException("Unable to decode the initial bind response " + 404 "from the server: response element had an " + 405 "invalid protocol op type.", 406 LDAPException.UNAVAILABLE); 407 } 408 409 elements = elements[1].decodeAsSequence().getElements(); 410 int resultCode = elements[0].decodeAsEnumerated().getIntValue(); 411 if (resultCode != LDAPException.SASL_BIND_IN_PROGRESS) 412 { 413 throw new LDAPException("Unable to decode the initial bind response " + 414 "from the server: inappropriate result code.", 415 LDAPException.UNAVAILABLE); 416 } 417 418 for (int i=1; i < elements.length; i++) 419 { 420 if (elements[i].getType() == LDAP_SERVER_SASL_CREDENTIALS_TYPE) 421 { 422 responseData = elements[i].decodeAsOctetString().getStringValue(); 423 } 424 } 425 426 if (responseData == null) 427 { 428 throw new LDAPException("Unable to decode the initial bind response " + 429 "from the server: could not obtain the " + 430 "server SASL credentials.", 431 LDAPException.UNAVAILABLE); 432 } 433 } 434 catch (ASN1Exception ae) 435 { 436 throw new LDAPException("Unable to decode the initial bind response " + 437 "from the server: " + ae, 438 LDAPException.UNAVAILABLE); 439 } 440 441 442 StringTokenizer tokenizer = new StringTokenizer(responseData, ","); 445 String nonce = null; 446 String realm = null; 447 String charSet = "utf-8"; 448 while (tokenizer.hasMoreTokens()) 449 { 450 String token = tokenizer.nextToken(); 451 int equalPos = token.indexOf("="); 452 String tokenName = token.substring(0, equalPos).toLowerCase(); 453 String tokenValue = token.substring(equalPos+1); 454 if (tokenValue.startsWith("\"")) 455 { 456 tokenValue = tokenValue.substring(1, (tokenValue.length() - 1)); 457 } 458 459 if (tokenName.equals("nonce")) 460 { 461 nonce = tokenValue; 462 } 463 else if (tokenName.equals("realm")) 464 { 465 realm = tokenValue; 466 } 467 else if (tokenName.equals("charset")) 468 { 469 charSet = tokenValue; 470 } 471 } 472 473 474 if ((nonce == null) || (nonce.length() == 0)) 476 { 477 throw new LDAPException("Unable to decode the initial bind response " + 478 "from the server: could not extract the nonce " + 479 "from the server SASL credentials.", 480 LDAPException.UNAVAILABLE); 481 } 482 else if ((realm == null) || (realm.length() == 0)) 483 { 484 throw new LDAPException("Unable to decode the initial bind response " + 485 "from the server: could not extract the realm " + 486 "from the server SASL credentials.", 487 LDAPException.UNAVAILABLE); 488 } 489 490 491 String cnonce = generateCNonce(Math.max(32, nonce.length())); 494 String nonceCount = "00000001"; 495 String qop = "auth"; 496 String digestURI = "ldap/" + host; 497 498 String response; 499 try 500 { 501 response = generateResponse(authID, password, realm, nonce, cnonce, 502 nonceCount, digestURI, charSet); 503 } 504 catch (Exception e) 505 { 506 throw new LDAPException("Internal failure while generating the " + 507 "response value to send to the server: " + e, 508 LDAPException.UNAVAILABLE); 509 } 510 511 512 String responseStr = "username=\"" + authID + "\",realm=\"" + realm + 514 "\",nonce=\"" + nonce + "\",cnonce=\"" + cnonce + 515 "\",nc=" + nonceCount + ",qop=" + qop + 516 ",digest-uri=\"" + digestURI + "\",response=" + 517 response; 518 519 520 saslCredentialElements = new ASN1Element[] 522 { 523 new ASN1OctetString(SASL_MECHANISM_NAME), 524 new ASN1OctetString(responseStr) 525 }; 526 527 bindRequestElements = new ASN1Element[] 528 { 529 new ASN1Integer(3), 530 new ASN1OctetString(), 531 new ASN1Sequence(LDAP_SASL_CREDENTIALS_TYPE, saslCredentialElements) 532 }; 533 534 ldapMessageElements = new ASN1Element[] 535 { 536 new ASN1Integer(2), 537 new ASN1Sequence(LDAP_BIND_REQUEST_TYPE, bindRequestElements) 538 }; 539 540 messageElement = new ASN1Sequence(ldapMessageElements); 541 542 543 try 545 { 546 asn1Writer.writeElement(messageElement); 547 } 548 catch (IOException ioe) 549 { 550 throw new LDAPException("Unable to send the subsequent bind request to " + 551 "the server: " + ioe, 552 LDAPException.CONNECT_ERROR); 553 } 554 555 556 try 558 { 559 responseElement = 560 asn1Reader.readElement(Constants.MAX_BLOCKING_READ_TIME); 561 } 562 catch (ASN1Exception ae) 563 { 564 throw new LDAPException("Unable to decode the subsequent bind response " + 565 "from the server: " + ae, 566 LDAPException.UNAVAILABLE); 567 } 568 catch (IOException ioe) 569 { 570 throw new LDAPException("Unable to read the subsequent bind response " + 571 "from the server: " + ioe, 572 LDAPException.CONNECT_ERROR); 573 } 574 575 576 try 578 { 579 ASN1Element[] elements = responseElement.decodeAsSequence().getElements(); 580 if (elements.length != 2) 581 { 582 throw new LDAPException("Unable to decode the subsequent bind " + 583 "response from the server: response element " + 584 "had an invalid number of elements.", 585 LDAPException.UNAVAILABLE); 586 } 587 588 if (elements[1].getType() != LDAP_BIND_RESPONSE_TYPE) 589 { 590 throw new LDAPException("Unable to decode the subsequent bind " + 591 "response from the server: response element " + 592 "had an invalid protocol op type.", 593 LDAPException.UNAVAILABLE); 594 } 595 596 elements = elements[1].decodeAsSequence().getElements(); 597 int resultCode = elements[0].decodeAsEnumerated().getIntValue(); 598 if (resultCode == LDAPException.SUCCESS) 599 { 600 return; 601 } 602 603 604 String matchedDN = elements[1].decodeAsOctetString().getStringValue(); 605 String errorMessage = elements[2].decodeAsOctetString().getStringValue(); 606 throw new LDAPException("The bind attempt was not successful.", 607 resultCode, errorMessage, matchedDN); 608 } 609 catch (ASN1Exception ae) 610 { 611 throw new LDAPException("Unable to decode the subsequent bind response " + 612 "from the server: " + ae, 613 LDAPException.UNAVAILABLE); 614 } 615 } 616 617 618 619 626 private String generateCNonce(int length) 627 { 628 char[] cnonceChars = new char[length]; 629 630 for (int i=0; i < cnonceChars.length; i++) 631 { 632 cnonceChars[i] = CNONCE_ALPHABET[(random.nextInt() & 0x7FFFFFFF) % 633 CNONCE_ALPHABET.length]; 634 } 635 636 return new String (cnonceChars); 637 } 638 639 640 641 661 private String generateResponse(String authID, String password, String realm, 662 String nonce, String cnonce, 663 String nonceCount, String digestURI, 664 String charset) 665 throws UnsupportedEncodingException 666 { 667 String a1Str1 = authID + ":" + realm + ":" + password; 668 byte[] a1bytes1 = md5Digest.digest(a1Str1.getBytes(charset)); 669 670 String a1Str2 = ":" + nonce + ":" + cnonce; 671 byte[] a1bytes2 = a1Str2.getBytes(charset); 672 673 byte[] a1 = new byte[a1bytes1.length + a1bytes2.length]; 674 System.arraycopy(a1bytes1, 0, a1, 0, a1bytes1.length); 675 System.arraycopy(a1bytes2, 0, a1, a1bytes1.length, a1bytes2.length); 676 677 byte[] a2 = ("AUTHENTICATE:" + digestURI).getBytes(charset); 678 679 String hexHashA1 = getHexString(md5Digest.digest(a1)); 680 String hexHashA2 = getHexString(md5Digest.digest(a2)); 681 682 String kdStr = hexHashA1 + ":" + nonce + ":" + nonceCount + ":" + cnonce + 683 ":" + QOP_AUTH + ":" + hexHashA2; 684 return getHexString(md5Digest.digest(kdStr.getBytes(charset))); 685 } 686 687 688 689 698 private String getHexString(byte[] bytes) 699 { 700 StringBuffer buffer = new StringBuffer (2 * bytes.length); 701 for (int i=0; i < bytes.length; i++) 702 { 703 switch ((bytes[i] >> 4) & 0x0F) 704 { 705 case 0x00: 706 buffer.append("0"); 707 break; 708 case 0x01: 709 buffer.append("1"); 710 break; 711 case 0x02: 712 buffer.append("2"); 713 break; 714 case 0x03: 715 buffer.append("3"); 716 break; 717 case 0x04: 718 buffer.append("4"); 719 break; 720 case 0x05: 721 buffer.append("5"); 722 break; 723 case 0x06: 724 buffer.append("6"); 725 break; 726 case 0x07: 727 buffer.append("7"); 728 break; 729 case 0x08: 730 buffer.append("8"); 731 break; 732 case 0x09: 733 buffer.append("9"); 734 break; 735 case 0x0a: 736 buffer.append("a"); 737 break; 738 case 0x0b: 739 buffer.append("b"); 740 break; 741 case 0x0c: 742 buffer.append("c"); 743 break; 744 case 0x0d: 745 buffer.append("d"); 746 break; 747 case 0x0e: 748 buffer.append("e"); 749 break; 750 case 0x0f: 751 buffer.append("f"); 752 break; 753 } 754 755 switch (bytes[i] & 0x0F) 756 { 757 case 0x00: 758 buffer.append("0"); 759 break; 760 case 0x01: 761 buffer.append("1"); 762 break; 763 case 0x02: 764 buffer.append("2"); 765 break; 766 case 0x03: 767 buffer.append("3"); 768 break; 769 case 0x04: 770 buffer.append("4"); 771 break; 772 case 0x05: 773 buffer.append("5"); 774 break; 775 case 0x06: 776 buffer.append("6"); 777 break; 778 case 0x07: 779 buffer.append("7"); 780 break; 781 case 0x08: 782 buffer.append("8"); 783 break; 784 case 0x09: 785 buffer.append("9"); 786 break; 787 case 0x0a: 788 buffer.append("a"); 789 break; 790 case 0x0b: 791 buffer.append("b"); 792 break; 793 case 0x0c: 794 buffer.append("c"); 795 break; 796 case 0x0d: 797 buffer.append("d"); 798 break; 799 case 0x0e: 800 buffer.append("e"); 801 break; 802 case 0x0f: 803 buffer.append("f"); 804 break; 805 } 806 } 807 808 return buffer.toString(); 809 } 810 } 811 812 | Popular Tags |