61 * settings (except for the <code>writeKtab()</code> method). However, to make |
61 * settings (except for the <code>writeKtab()</code> method). However, to make |
62 * sure nothing ever goes wrong, if you want to make any changes to these |
62 * sure nothing ever goes wrong, if you want to make any changes to these |
63 * settings after calling a KDC method, call <code>Config.refresh()</code> to |
63 * settings after calling a KDC method, call <code>Config.refresh()</code> to |
64 * make sure your changes are reflected in the <code>Config</code> object. |
64 * make sure your changes are reflected in the <code>Config</code> object. |
65 * </ol> |
65 * </ol> |
|
66 * System properties recognized: |
|
67 * <ul> |
|
68 * <li>test.kdc.save.ccache |
|
69 * </ul> |
|
70 * Support policies: |
|
71 * <ul> |
|
72 * <li>ok-as-delegate |
|
73 * </ul> |
66 * Issues and TODOs: |
74 * Issues and TODOs: |
67 * <ol> |
75 * <ol> |
68 * <li> Generates krb5.conf to be used on another machine, currently the kdc is |
76 * <li> Generates krb5.conf to be used on another machine, currently the kdc is |
69 * always localhost |
77 * always localhost |
70 * <li> More options to KDC, say, error output, say, response nonce != |
78 * <li> More options to KDC, say, error output, say, response nonce != |
149 |
157 |
150 /** |
158 /** |
151 * A standalone KDC server. |
159 * A standalone KDC server. |
152 */ |
160 */ |
153 public static void main(String[] args) throws Exception { |
161 public static void main(String[] args) throws Exception { |
154 KDC kdc = create("RABBIT.HOLE", "kdc.rabbit,hole", 0, false); |
162 KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", 0, false); |
155 kdc.addPrincipal("dummy", "bogus".toCharArray()); |
163 kdc.addPrincipal("dummy", "bogus".toCharArray()); |
156 kdc.addPrincipal("foo", "bar".toCharArray()); |
164 kdc.addPrincipal("foo", "bar".toCharArray()); |
157 kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE"); |
165 kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE"); |
158 kdc.addPrincipalRandKey("server/host.rabbit.hole"); |
166 kdc.addPrincipalRandKey("server/host.rabbit.hole"); |
159 kdc.addPrincipalRandKey("backend/host.rabbit.hole"); |
167 kdc.addPrincipalRandKey("backend/host.rabbit.hole"); |
424 * @param p principal |
432 * @param p principal |
425 * @return the password |
433 * @return the password |
426 * @throws sun.security.krb5.KrbException when the principal is not inside |
434 * @throws sun.security.krb5.KrbException when the principal is not inside |
427 * the database. |
435 * the database. |
428 */ |
436 */ |
429 private char[] getPassword(PrincipalName p) throws KrbException { |
437 private char[] getPassword(PrincipalName p, boolean server) |
|
438 throws KrbException { |
430 String pn = p.toString(); |
439 String pn = p.toString(); |
431 if (p.getRealmString() == null) { |
440 if (p.getRealmString() == null) { |
432 pn = pn + "@" + getRealm(); |
441 pn = pn + "@" + getRealm(); |
433 } |
442 } |
434 char[] pass = passwords.get(pn); |
443 char[] pass = passwords.get(pn); |
435 if (pass == null) { |
444 if (pass == null) { |
436 throw new KrbException(Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN); |
445 throw new KrbException(server? |
|
446 Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN: |
|
447 Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN); |
437 } |
448 } |
438 return pass; |
449 return pass; |
439 } |
450 } |
440 |
451 |
441 /** |
452 /** |
455 |
466 |
456 /** |
467 /** |
457 * Returns the key for a given principal of the given encryption type |
468 * Returns the key for a given principal of the given encryption type |
458 * @param p the principal |
469 * @param p the principal |
459 * @param etype the encryption type |
470 * @param etype the encryption type |
|
471 * @param server looking for a server principal? |
460 * @return the key |
472 * @return the key |
461 * @throws sun.security.krb5.KrbException for unknown/unsupported etype |
473 * @throws sun.security.krb5.KrbException for unknown/unsupported etype |
462 */ |
474 */ |
463 private EncryptionKey keyForUser(PrincipalName p, int etype) throws KrbException { |
475 private EncryptionKey keyForUser(PrincipalName p, int etype, boolean server) |
|
476 throws KrbException { |
464 try { |
477 try { |
465 // Do not call EncryptionKey.acquireSecretKeys(), otherwise |
478 // Do not call EncryptionKey.acquireSecretKeys(), otherwise |
466 // the krb5.conf config file would be loaded. |
479 // the krb5.conf config file would be loaded. |
467 Method stringToKey = EncryptionKey.class.getDeclaredMethod("stringToKey", char[].class, String.class, byte[].class, Integer.TYPE); |
480 Method stringToKey = EncryptionKey.class.getDeclaredMethod("stringToKey", char[].class, String.class, byte[].class, Integer.TYPE); |
468 stringToKey.setAccessible(true); |
481 stringToKey.setAccessible(true); |
469 Integer kvno = null; |
482 Integer kvno = null; |
470 // For service whose password ending with a number, use it as kvno |
483 // For service whose password ending with a number, use it as kvno |
471 if (p.toString().indexOf('/') >= 0) { |
484 if (p.toString().indexOf('/') >= 0) { |
472 char[] pass = getPassword(p); |
485 char[] pass = getPassword(p, server); |
473 if (Character.isDigit(pass[pass.length-1])) { |
486 if (Character.isDigit(pass[pass.length-1])) { |
474 kvno = pass[pass.length-1] - '0'; |
487 kvno = pass[pass.length-1] - '0'; |
475 } |
488 } |
476 } |
489 } |
477 return new EncryptionKey((byte[]) stringToKey.invoke( |
490 return new EncryptionKey((byte[]) stringToKey.invoke( |
478 null, getPassword(p), getSalt(p), null, etype), |
491 null, getPassword(p, server), getSalt(p), null, etype), |
479 etype, kvno); |
492 etype, kvno); |
480 } catch (InvocationTargetException ex) { |
493 } catch (InvocationTargetException ex) { |
481 KrbException ke = (KrbException)ex.getCause(); |
494 KrbException ke = (KrbException)ex.getCause(); |
482 throw ke; |
495 throw ke; |
|
496 } catch (KrbException ke) { |
|
497 throw ke; |
483 } catch (Exception e) { |
498 } catch (Exception e) { |
484 throw new RuntimeException(e); // should not happen |
499 throw new RuntimeException(e); // should not happen |
485 } |
500 } |
486 } |
501 } |
|
502 |
|
503 private Map<String,String> policies = new HashMap<String,String>(); |
|
504 |
|
505 public void setPolicy(String rule, String value) { |
|
506 if (value == null) { |
|
507 policies.remove(rule); |
|
508 } else { |
|
509 policies.put(rule, value); |
|
510 } |
|
511 } |
|
512 /** |
|
513 * If the provided client/server pair matches a rule |
|
514 * |
|
515 * A system property named test.kdc.policy.RULE will be consulted. |
|
516 * If it's unset, returns false. If its value is "", any pair is |
|
517 * matched. Otherwise, it should contains the server name matched. |
|
518 * |
|
519 * TODO: client name is not used currently. |
|
520 * |
|
521 * @param c client name |
|
522 * @param s server name |
|
523 * @param rule rule name |
|
524 * @return if a match is found |
|
525 */ |
|
526 private boolean configMatch(String c, String s, String rule) { |
|
527 String policy = policies.get(rule); |
|
528 boolean result = false; |
|
529 if (policy == null) { |
|
530 result = false; |
|
531 } else if (policy.length() == 0) { |
|
532 result = true; |
|
533 } else { |
|
534 String[] names = policy.split("\\s+"); |
|
535 for (String name: names) { |
|
536 if (name.equals(s)) { |
|
537 result = true; |
|
538 break; |
|
539 } |
|
540 } |
|
541 } |
|
542 if (result) { |
|
543 System.out.printf(">>>> Policy match result (%s vs %s on %s) %b\n", |
|
544 c, s, rule, result); |
|
545 } |
|
546 return result; |
|
547 } |
|
548 |
487 |
549 |
488 /** |
550 /** |
489 * Processes an incoming request and generates a response. |
551 * Processes an incoming request and generates a response. |
490 * @param in the request |
552 * @param in the request |
491 * @return the response |
553 * @return the response |
528 APReq apReq = new APReq(pa.getValue()); |
590 APReq apReq = new APReq(pa.getValue()); |
529 EncryptedData ed = apReq.authenticator; |
591 EncryptedData ed = apReq.authenticator; |
530 tkt = apReq.ticket; |
592 tkt = apReq.ticket; |
531 etype = tkt.encPart.getEType(); |
593 etype = tkt.encPart.getEType(); |
532 tkt.sname.setRealm(tkt.realm); |
594 tkt.sname.setRealm(tkt.realm); |
533 EncryptionKey kkey = keyForUser(tkt.sname, etype); |
595 EncryptionKey kkey = keyForUser(tkt.sname, etype, true); |
534 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET); |
596 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET); |
535 DerInputStream derIn = new DerInputStream(bb); |
597 DerInputStream derIn = new DerInputStream(bb); |
536 DerValue der = derIn.getDerValue(); |
598 DerValue der = derIn.getDerValue(); |
537 etp = new EncTicketPart(der.toByteArray()); |
599 etp = new EncTicketPart(der.toByteArray()); |
538 } |
600 } |
539 } |
601 } |
540 if (tkt == null) { |
602 if (tkt == null) { |
541 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); |
603 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); |
542 } |
604 } |
543 } |
605 } |
544 EncryptionKey skey = keyForUser(body.sname, etype); |
606 EncryptionKey skey = keyForUser(body.sname, etype, true); |
545 if (skey == null) { |
607 if (skey == null) { |
546 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO |
608 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO |
547 } |
609 } |
548 |
610 |
549 // Session key for original ticket, TGT |
611 // Session key for original ticket, TGT |
669 Field f = KDCReqBody.class.getDeclaredField("eType"); |
735 Field f = KDCReqBody.class.getDeclaredField("eType"); |
670 f.setAccessible(true); |
736 f.setAccessible(true); |
671 eTypes = (int[])f.get(body); |
737 eTypes = (int[])f.get(body); |
672 int eType = eTypes[0]; |
738 int eType = eTypes[0]; |
673 |
739 |
674 EncryptionKey ckey = keyForUser(body.cname, eType); |
740 EncryptionKey ckey = keyForUser(body.cname, eType, false); |
675 EncryptionKey skey = keyForUser(body.sname, eType); |
741 EncryptionKey skey = keyForUser(body.sname, eType, true); |
676 if (ckey == null) { |
742 if (ckey == null) { |
677 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); |
743 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); |
678 } |
744 } |
679 if (skey == null) { |
745 if (skey == null) { |
680 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO |
746 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO |