xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/AuthBlock.java (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * ident	"%Z%%M%	%I%	%E% SMI"
24  *
25  * Copyright (c) 1999 by Sun Microsystems, Inc.
26  * All rights reserved.
27  *
28  */
29 
30 package com.sun.slp;
31 
32 import java.util.*;
33 import java.io.*;
34 import java.security.*;
35 import java.security.cert.*;
36 
37 /**
38  * The AuthBlock class models both the client and server side
39  * authentication blocks.
40  *<p>
41  * AuthBlocks are agnostic as to which components from a given
42  * message should be used in authentication. Thus each message
43  * must provide the correct components in the correct order.
44  *<p>
45  * These components are passed via Object[]s. The Object[] elements
46  * should be in externalized form, and should be ordered as stated
47  * in the protocol specification for auth blocks. AuthBlocks will
48  * add the externalized SPI string before the Object[] and the
49  * externalized timestamp after the vector.
50  *<p>
51  * The AuthBlock class provides a number of static convenience
52  * methods which operate on sets of AuthBlocks. The sets of
53  * AuthBlocks are stored in Hashtables, keyed by SPIs.
54  */
55 
56 class AuthBlock {
57 
58     static private String SPI_PROPERTY = "sun.net.slp.SPIs";
59 
60     /**
61      * A convenience method for creating a set of auth blocks
62      * from internal data structures.
63      *
64      * @param message The ordered components of the SLP message
65      *			over which the signature should be computed,
66      *			in externalized (byte[]) form.
67      * @param lifetime The lifetime for this message, in seconds.
68      * @return A Hashtable of AuthBlocks, one for each SPI, null if no
69      *		SPIs have been configured.
70      * @exception ServiceLocationException If a key management or crypto
71      *					algorithm provider cannot be
72      *					instantiated, a SYSTEM_ERROR exception
73      *					is thrown.
74      * @exception IllegalArgumentException If any of the parameters are null
75      *					or empty.
76      */
77     static Hashtable makeAuthBlocks(Object[] message, int lifetime)
78 	throws ServiceLocationException, IllegalArgumentException {
79 
80 	Hashtable spis = getSignAs();
81 	if (spis == null) {
82 	    throw new ServiceLocationException(
83 		ServiceLocationException.AUTHENTICATION_FAILED,
84 		"cant_sign", new Object[0]);
85 	}
86 
87 	Hashtable blocks = new Hashtable();
88 	Enumeration spisEnum = spis.keys();
89 	while (spisEnum.hasMoreElements()) {
90 	    String spi = (String) spisEnum.nextElement();
91 	    int bsd = ((Integer)(spis.get(spi))).intValue();
92 	    blocks.put(spi, new AuthBlock(message, spi, bsd, lifetime));
93 	}
94 	return blocks;
95     }
96 
97     /**
98      * A convenience method which creates a Hashtable of auth blocks
99      * from an input stream.
100      *
101      * @param hdr Header of message being parsed out.
102      * @param message The ordered components of the SLP message
103      *			over which the signature should have been computed,
104      *			in externalized (byte[]) form.
105      * @param dis Input stream with the auth block bytes queued up as the
106      *			next thing.
107      * @param nBlocks Number of auth blocks to read.
108      * @return A Hashtable of AuthBlocks.
109      * @exception ServiceLocationException If anything goes wrong during
110      *					parsing. If nBlocks is 0, the
111      *					error code is AUTHENTICATION_ABSENT.
112      * @exception IllegalArgumentException If any of the parameters are null
113      *					or empty.
114      * @exception IOException If DataInputStream throws it.
115      */
116     static Hashtable makeAuthBlocks(SrvLocHeader hdr,
117 				    Object[] message,
118 				    DataInputStream dis,
119 				    byte nBlocks)
120 	throws ServiceLocationException,
121 	       IllegalArgumentException,
122 	       IOException {
123 
124 	Hashtable blocks = new Hashtable();
125 
126 	for (byte cnt = 0; cnt < nBlocks; cnt++) {
127 	    AuthBlock ab = new AuthBlock(hdr, message, dis);
128 	    blocks.put(ab.getSPI(), ab);
129 	}
130 
131 	return blocks;
132     }
133 
134     /**
135      * A convenience method which verifies all auth blocks in the
136      * input Hashtable.
137      *
138      * @param authBlocks A Hashtable containing AuthBlocks.
139      * @exception ServiceLocationException Thrown if authentication fails,
140      *            with the error code
141      *            ServiceLocationException.AUTHENTICATION_FAILED. If any
142      *            other error occurs during authentication, the
143      *            error code is ServiceLocationException.SYSTEM_ERROR.
144      *            If the signature hasn't been calculated the
145      *		   authentication fails.
146      * @exception IllegalArgumentException If authBlocks is null or empty.
147      */
148     static void verifyAll(Hashtable authBlocks)
149 	throws ServiceLocationException, IllegalArgumentException {
150 
151 	ensureNonEmpty(authBlocks, "authBlocks");
152 
153 	Enumeration blocks = authBlocks.elements();
154 
155 	while (blocks.hasMoreElements()) {
156 	    AuthBlock ab = (AuthBlock) blocks.nextElement();
157 	    ab.verify();
158 	}
159     }
160 
161     /**
162      * A convenience method which finds the shortest lifetime in a
163      * set of AuthBlocks.
164      *
165      * @param authBlocks A Hashtable containing AuthBlocks.
166      * @return The shortest lifetime found.
167      * @exception IllegalArgumentException If authBlocks is null or empty.
168      */
169     static int getShortestLifetime(Hashtable authBlocks)
170 	    throws IllegalArgumentException {
171 
172 	ensureNonEmpty(authBlocks, "authBlocks");
173 
174 	Enumeration blocks = authBlocks.elements();
175 	int lifetime = Integer.MAX_VALUE;
176 
177 	while (blocks.hasMoreElements()) {
178 	    AuthBlock ab = (AuthBlock) blocks.nextElement();
179 	    int abLife = ab.getLifetime();
180 	    lifetime = (lifetime < abLife) ? lifetime : abLife;
181 	}
182 
183 	return lifetime;
184     }
185 
186     /**
187      * A convenience method which externalizes a set of AuthBlocks
188      * into a ByteArrayOutputStream. The number of blocks is NOT
189      * written onto the stream.
190      *
191      * @param hdr Header of message being externalized.
192      * @param authBlocks A Hashtable containing AuthBlocks.
193      * @param baos The output stream into which to write.
194      * @exception ServiceLocationException Thrown if an error occurs during
195      *					  output, with PARSE_ERROR error code.
196      * @exception IllegalArgumentException If any parameters are null, or
197      *					  if authBlocks is empty.
198      */
199     static void externalizeAll(SrvLocHeader hdr,
200 			       Hashtable authBlocks,
201 			       ByteArrayOutputStream baos)
202 	throws ServiceLocationException, IllegalArgumentException {
203 
204 	ensureNonEmpty(authBlocks, "authBlocks");
205 
206 	Enumeration blocks = authBlocks.elements();
207 
208 	while (blocks.hasMoreElements()) {
209 	    AuthBlock ab = (AuthBlock) blocks.nextElement();
210 	    ab.externalize(hdr, baos);
211 	}
212     }
213 
214     /**
215      * Returns the message parts obtained from the AuthBlock contructor.
216      * The Object[] will not have been altered. Note that all AuthBlocks
217      * contain the same message Object[] Object.
218      *
219      * @param authBlocks A Hashtable containing AuthBlocks.
220      * @return This auth block's message components Object[].
221      * @exception IllegalArgumentException If authBlocks is null or empty.
222      */
223     static Object[] getContents(Hashtable authBlocks)
224 	throws IllegalArgumentException {
225 
226 	ensureNonEmpty(authBlocks, "authBlocks");
227 
228 	Enumeration blocks = authBlocks.elements();
229 	AuthBlock ab = (AuthBlock) blocks.nextElement();
230 	return ab.getMessageParts();
231     }
232 
233     /**
234      * Creates a String describing all auth blocks in authBlocks.
235      * We dont't use toString() since that would get Hashtable.toString(),
236      * and we can format it a little prettier.
237      *
238      * @param authBlocks A Hashtable containing AuthBlocks.
239      * @return A String description of all AuthBlocks in this Hashtable
240      */
241     static String desc(Hashtable authBlocks) {
242 
243 	if (authBlocks == null) {
244 	    return "null";
245 	}
246 
247 	Enumeration blocks = authBlocks.elements();
248 	int size = authBlocks.size();
249 	String desc = size == 1 ? "1 Auth Block:\n" : size + " Auth Blocks:\n";
250 	int cnt = 0;
251 
252 	while (blocks.hasMoreElements()) {
253 	    AuthBlock ab = (AuthBlock) blocks.nextElement();
254 	    desc = desc + "             " + (cnt++) + ": " + ab.toString();
255 	}
256 
257 	return desc;
258     }
259 
260     /**
261      * Returns the list of SPIs configured with this 'prop', or null
262      * if the property hasn't been set.
263      */
264     static LinkedList getSPIList(String prop) {
265 	String spiProp = System.getProperty(prop);
266 	if (spiProp == null) {
267 	    return null;
268 	}
269 
270 	return commaSeparatedListToLinkedList(spiProp);
271     }
272 
273     /**
274      * Converts a comma-separaterd list in a String to a LinkedList.
275      */
276     static LinkedList commaSeparatedListToLinkedList(String listStr) {
277 	StringTokenizer stk_comma = new StringTokenizer(listStr, ",");
278 	LinkedList answer = new LinkedList();
279 	while (stk_comma.hasMoreTokens()) {
280 	    String spi = stk_comma.nextToken();
281 	    answer.add(spi);
282 	}
283 
284 	return answer;
285     }
286 
287     /**
288      * Returns true if this principal is someDH, or if this principal's
289      * cert has been signed by someDN.
290      */
291     static boolean canSignAs(String someDN) throws ServiceLocationException {
292 	X509Certificate myCert = getSignAsCert();
293 	if (myCert == null) {
294 	    return false;
295 	}
296 
297 	KeyStore ks = getKeyStore();
298 	if (ks == null) {
299 	    return false;
300 	}
301 
302 	X509Certificate cert = getCert(someDN, ks);
303 
304 	return onCertChain(
305 		myCert.getSubjectDN().toString(), cert.getSubjectDN());
306     }
307 
308     /**
309      * Checks if caDN is in ab's equivalency set, i.e. if caDN
310      * is in ab's cert chain.
311      */
312     static boolean checkEquiv(String caDN, AuthBlock ab) {
313 	// Get cert for input DN
314 	X509Certificate caCert;
315 	try {
316 	    KeyStore ks = getKeyStore();
317 
318 	    caCert = getCert(caDN, ks);
319 	} catch (Exception e) {
320 	    SLPConfig.getSLPConfig().writeLog(
321 		"cant_get_equivalency",
322 		new Object[] {caDN, e.getMessage()});
323 	    return false;
324 	}
325 
326 	return ab.inEqSet(caCert.getSubjectDN());
327     }
328 
329     /**
330      * Filters out from auths all auth blocks which have not been
331      * signed by DNs equivalent to caDN.
332      */
333     static AuthBlock getEquivalentAuth(String caDN, Hashtable authBlocks) {
334 	if (authBlocks.size() == 0) {
335 	    return null;
336 	}
337 
338 	// Get cert for input DN
339 	X509Certificate caCert;
340 	try {
341 	    KeyStore ks = getKeyStore();
342 
343 	    caCert = getCert(caDN, ks);
344 	} catch (Exception e) {
345 	    SLPConfig.getSLPConfig().writeLog(
346 		"cant_get_equivalency",
347 		new Object[] { caDN, e.getMessage()});
348 	    return null;
349 	}
350 
351 	Enumeration blocks = authBlocks.elements();
352 
353 	while (blocks.hasMoreElements()) {
354 	    AuthBlock ab = (AuthBlock) blocks.nextElement();
355 	    if (ab.inEqSet(caCert.getSubjectDN())) {
356 		return ab;
357 	    }
358 	}
359 
360 	return null;
361     }
362 
363 
364     /**
365      * Gets a list of signing identities. Returns a Hashtable of
366      * which the keys are SPI strings (DNs) and the values
367      * are BSD Integers.
368      */
369     static Hashtable getSignAs() throws ServiceLocationException {
370 	X509Certificate cert = getSignAsCert();
371 	Hashtable answer = new Hashtable();
372 
373 	if (cert == null) {
374 	    return null;
375 	}
376 
377 	/* derive DN from alias */
378 	String DN = cert.getSubjectDN().toString();
379 	String e_DN = null;
380 	// escape DN
381 	try {
382 	    e_DN = ServiceLocationAttribute.escapeAttributeString(DN, false);
383 	} catch (ServiceLocationException e) {
384 	    // Shouldn't get here if badTag == false
385 	    e_DN = DN;
386 	}
387 	DN = e_DN;
388 
389 	String alg = cert.getPublicKey().getAlgorithm();
390 	int ibsd;
391 	if (alg.equals("DSA")) {
392 	    ibsd = 2;
393 	} else if (alg.equals("RSA")) {
394 	    ibsd = 1;
395 	} else {
396 	    SLPConfig.getSLPConfig().writeLog("bad_alg_for_alias",
397 					      new Object[] {alg});
398 	    return null;
399 	}
400 
401 	answer.put(DN, new Integer(ibsd));
402 
403 	return answer;
404     }
405 
406     /**
407      * Returns the cert corresponding to our signing alias.
408      * @@@ change this when AMI goes in to use private AMI interface.
409      */
410     static X509Certificate getSignAsCert() throws ServiceLocationException {
411 	String spiProp = System.getProperty("sun.net.slp.signAs");
412 	if (spiProp == null) {
413 	    SLPConfig.getSLPConfig().writeLog(
414 		"no_spis_given", new Object[0]);
415 	    return null;
416 	}
417 
418 	/* load key store */
419 	KeyStore ks = getKeyPkg();
420 
421 	StringTokenizer stk_comma = new StringTokenizer(spiProp, ",");
422 	X509Certificate cert = null;
423 
424 	// Can only sign with one alias, so ignore any extras
425 	if (stk_comma.hasMoreTokens()) {
426 	    String alias = stk_comma.nextToken();
427 
428 	    /* get keypkg for this alias */
429 	    cert = getCert(alias, ks);
430 	}
431 
432 	return cert;
433     }
434 
435     /**
436      * Creates a new AuthBlock based on the SPI and message parts.
437      *
438      * @param message The ordered components of the SLP message
439      *			over which the signature should be computed,
440      *			in externalized (byte[]) form.
441      * @param spi The SLP SPI for which to create the auth block.
442      * @param lifetime The lifetime for this message, in seconds.
443      * @exception ServiceLocationException If a key management or crypto
444      *					algorithm provider cannot be
445      *					instantiated, a SYSTEM_ERROR exception
446      *					is thrown.
447      * @exception IllegalArgumentException If any of the parameters are null
448      *					or empty.
449      */
450     AuthBlock(Object[] message, String spi, int bsd, int lifetime)
451 	throws ServiceLocationException, IllegalArgumentException {
452 
453 	ensureNonEmpty(message, "message");
454 	Assert.nonNullParameter(spi, "spi");
455 
456 	// init crypto provider associated with bsd
457 	this.bsd = bsd;
458 	getSecurityProvider(bsd);
459 
460 	this.message = message;
461 	this.spi = spi;
462 	this.lifetime = lifetime;
463 	this.timeStamp = SLPConfig.currentSLPTime() + lifetime;
464 
465 	// Create the signature: create and sign the hash
466 
467 	try {
468 	    // @@@ how to sign for different aliases?
469 	    sig.initSign(null);
470 	    computeHash();
471 	    abBytes = sig.sign();
472 	} catch (InvalidKeyException e) {	// @@@ will change for AMI
473 	  SLPConfig conf = SLPConfig.getSLPConfig();
474 	    throw
475 		new IllegalArgumentException(
476 				conf.formatMessage(
477 					"cant_sign_for_spi",
478 					new Object[] {
479 						spi,
480 						e.getMessage() }));
481 	} catch (SignatureException e) {
482 	  SLPConfig conf = SLPConfig.getSLPConfig();
483 	    throw
484 		new IllegalArgumentException(
485 				conf.formatMessage(
486 					"cant_sign_for_spi",
487 					new Object[] {
488 						spi,
489 						e.getMessage() }));
490 	}
491 
492 	// calculate the length
493 	abLength =
494 		2 + // bsd
495 		2 + // length
496 		4 + // timestamp
497 		spiBytes.length + // externalized SPI string, with length
498 		abBytes.length; // structured auth block
499     }
500 
501     /**
502      * Creates a new AuthBlock from an input stream.
503      *
504      * @param hdr The header of the message being parsed.
505      * @param message The ordered components of the SLP message
506      *			over which the signature should have been computed,
507      *			in externalized (byte[]) form.
508      * @param dis Input stream with the auth block bytes queued up as the
509      *			next thing.
510      * @exception ServiceLocationException If anything goes wrong during
511      *					parsing. If nBlocks is 0, the
512      *					error code is AUTHENTICATION_ABSENT.
513      * @exception IllegalArgumentException If any of the parameters are null
514      *					or empty.
515      * @exception IOException If DataInputStream throws it.
516      */
517     AuthBlock(SrvLocHeader hdr, Object[] message, DataInputStream dis)
518 	throws ServiceLocationException,
519 	       IllegalArgumentException,
520 	       IOException {
521 
522 	Assert.nonNullParameter(hdr, "hdr");
523 	ensureNonEmpty(message, "message");
524 	Assert.nonNullParameter(dis, "dis");
525 
526 	this.message = message;
527 	this.eqSet = new HashSet();
528 
529 	// parse in the auth block from the input stream;
530 	// first get the BSD and length
531 	bsd = hdr.getInt(dis);
532 	abLength = hdr.getInt(dis);
533 
534 	int pos = 4;	// bsd and length have already been consumed
535 
536 	// get the timestamp
537 	timeStamp = getInt32(dis);
538 	pos += 4;
539 	hdr.nbytes += 4;
540 
541 	// get the SPI
542 	StringBuffer buf = new StringBuffer();
543 	hdr.getString(buf, dis);
544 	spi = buf.toString();
545 	if (spi.length() == 0) {
546 		throw new ServiceLocationException(
547 		    ServiceLocationException.PARSE_ERROR,
548 		    "no_spi_string",
549 		    new Object[0]);
550 	}
551 	pos += (2 + spi.length());
552 
553 	// get the structured auth block
554 	abBytes = new byte[abLength - pos];
555 	dis.readFully(abBytes, 0, abLength - pos);
556 	hdr.nbytes += abBytes.length;
557 
558 	// calculate remaining lifetime from timestamp
559 	long time = timeStamp - SLPConfig.currentSLPTime();
560 	time = time <= Integer.MAX_VALUE ? time : 0;	// no crazy values
561 	lifetime = (int) time;
562 	lifetime = lifetime < 0 ? 0 : lifetime;
563 
564 	// Initialize the crypto provider
565 	getSecurityProvider(bsd);
566     }
567 
568     /**
569      * Gets the size of this auth block, after externalization, in bytes.
570      *
571      * @return The number of bytes in this auth block.
572      */
573     int nBytes() {
574 	return abLength;
575     }
576 
577     /**
578      * Returns the message parts obtained from the AuthBlock contructor.
579      * The Object[] will not have been altered.
580      *
581      * @return This auth block's message components Object[].
582      */
583     Object[] getMessageParts() {
584 	return message;
585     }
586 
587     /**
588      * Verifies the signature on this auth block.
589      *
590      * @exception ServiceLocationException Thrown if authentication fails,
591      *            with the error code
592      *            ServiceLocationException.AUTHENTICATION_FAILED. If any
593      *            other error occurs during authentication, the
594      *            error code is ServiceLocationException.SYSTEM_ERROR.
595      *            If the signature hasn't been calculated, the
596      *		   fails.
597      */
598     void verify() throws ServiceLocationException {
599 	// Load the keystore
600 	KeyStore ks = null;
601 	try {
602 	    ks = KeyStore.getInstance("amicerts", "SunAMI");
603 	    ks.load(null, null);
604 	} catch (Exception e) {
605 	    throw
606 		new ServiceLocationException(
607 			ServiceLocationException.AUTHENTICATION_FAILED,
608 			"no_keystore",
609 			new Object[] {e.getMessage()});
610 	}
611 
612 	// Unescape the SPI for cleaner logging
613 	String u_DN = null;
614 	try {
615 	    u_DN =
616 		ServiceLocationAttribute.unescapeAttributeString(spi, false);
617 	} catch (ServiceLocationException e) {
618 	    u_DN = spi;
619 	}
620 
621 	// get cert for this spi
622 	X509Certificate cert = getCert(spi, ks);
623 
624 	// check cert validity
625 	try {
626 	    cert.checkValidity();
627 	} catch (CertificateException e) {
628 	    throw new ServiceLocationException(
629 		ServiceLocationException.AUTHENTICATION_FAILED,
630 		"invalid_cert",
631 		new Object[] {u_DN, e.getMessage()});
632 	}
633 
634 	// check the lifetime
635 	if (lifetime == 0) {
636 	    throw new ServiceLocationException(
637 		ServiceLocationException.AUTHENTICATION_FAILED,
638 		"timestamp_failure",
639 		new Object[] {u_DN});
640 	}
641 
642 	// make sure this SPI matches up with configured SPIs
643 	try {
644 	    checkSPIs(cert, ks);
645 	} catch (GeneralSecurityException e) {
646 	    throw new ServiceLocationException(
647 		ServiceLocationException.AUTHENTICATION_FAILED,
648 		"cant_match_spis",
649 		new Object[] {cert.getSubjectDN(), e.getMessage()});
650 	}
651 
652 
653 	// check the signature
654 	try {
655 	    sig.initVerify(cert.getPublicKey());
656 	} catch (InvalidKeyException ex) {
657 	    throw
658 		new ServiceLocationException(
659 			ServiceLocationException.INTERNAL_SYSTEM_ERROR,
660 			"init_verify_failure",
661 			new Object[] {
662 				u_DN,
663 				    ex.getMessage()});
664 	}
665 
666 	computeHash();
667 
668 	ServiceLocationException vex =
669 	    new ServiceLocationException(
670 		ServiceLocationException.AUTHENTICATION_FAILED,
671 		"verify_failure",
672 		new Object[] {u_DN});
673 
674 	try {
675 	    if (!sig.verify(abBytes))
676 		throw vex;
677 	} catch (SignatureException ex) {
678 	    throw vex;
679 	}
680     }
681 
682     /**
683      * Convert the auth block into its on-the-wire format.
684      *
685      * @param hdr The header of the message being parsed out.
686      * @param baos The output stream into which to write.
687      * @exception ServiceLocationException Thrown if an error occurs during
688      *					  output, with PARSE_ERROR error code.
689      * @exception IllegalArgumentException If any baos is null.
690      */
691     void externalize(SrvLocHeader hdr, ByteArrayOutputStream baos)
692 	throws ServiceLocationException, IllegalArgumentException {
693 
694 	Assert.nonNullParameter(hdr, "hdr");
695 	Assert.nonNullParameter(baos, "baos");
696 
697 	// Lay out the auth block, starting with the BSD
698 	hdr.putInt(bsd, baos);
699 
700 	// write out the length
701 	hdr.putInt(abLength, baos);
702 
703 	// calculate and write out the timestamp
704 	putInt32(timeStamp, baos);
705 	hdr.nbytes += 4;
706 
707 	// write the SPI string
708 	hdr.putString(spi, baos);
709 
710 	// Finish by writting the structured auth block
711 	baos.write(abBytes, 0, abBytes.length);
712 	hdr.nbytes += abBytes.length;
713     }
714 
715     /**
716      * Returns the SPI associated with this auth block.
717      *
718      * @return The SLP SPI for this auth block.
719      */
720     String getSPI() {
721 	return spi;
722     }
723 
724     /**
725      * Returns the lifetime computed from this auth block.
726      *
727      * @return The lifetime from this auth block.
728      */
729     int getLifetime() {
730 	return lifetime;
731     }
732 
733     /**
734      * Given a BSD, sets this AuthBlock's Signature to the
735      * right algorithm.
736      */
737     private void getSecurityProvider(int bsd)
738 	throws ServiceLocationException {
739 
740 	String algo = "Unknown BSD";
741 	try {
742 	    if (bsd == 2) {
743 		// get DSA/SHA1 provider
744 		algo = "DSA";
745 		sig = Signature.getInstance("SHA/DSA", "SunAMI");
746 		return;
747 	    } else if (bsd == 1) {
748 		algo = "MD5/RSA";
749 		sig = Signature.getInstance("MD5/RSA", "SunAMI");
750 		return;
751 	    } else if (bsd == 3) {
752 		algo = "Keyed HMAC with MD5";
753 	    }
754 	} catch (GeneralSecurityException e) {
755 	    // system error -- no such provider
756 	    throw new ServiceLocationException(
757 		ServiceLocationException.INTERNAL_SYSTEM_ERROR,
758 		"cant_get_security_provider",
759 		new Object[] {
760 			new Integer(bsd),
761 			algo,
762 			e.getMessage()});
763 	}
764 
765 	// Unknown or unsupported BSD
766 	throw new ServiceLocationException(
767 	    ServiceLocationException.INTERNAL_SYSTEM_ERROR,
768 	    "cant_get_security_provider",
769 	    new Object[] {
770 		new Integer(bsd),
771 		algo,
772 		"Unknown or unsupported BSD"});
773     }
774 
775     /**
776      * throws an IllegalArgumentException if v is null or empty.
777      * v can be either a Hashtable or a Object[].
778      */
779     static private void ensureNonEmpty(Object v, String param)
780 	throws IllegalArgumentException {
781 
782 	int size = 0;
783 	if (v != null) {
784 	    if (v instanceof Object[]) {
785 		size = ((Object[]) v).length;
786 	    } else {
787 		// this will force a class cast exception if not a Hashtable
788 		size = ((Hashtable) v).size();
789 	    }
790 	}
791 
792 	if (v == null || size == 0) {
793 	    SLPConfig conf = SLPConfig.getSLPConfig();
794 	    String msg =
795 		conf.formatMessage("null_or_empty_vector",
796 				   new Object[] {param});
797 	    throw
798 		new IllegalArgumentException(msg);
799 	}
800     }
801 
802     /**
803      * Computes a hash over the SPI String, message componenets,
804      * and timstamp. Which hash is used depends on which crypto
805      * provider was installed.
806      *
807      * This method assumes that the class variables spi, sig,
808      * message, and timeStamp have all been initialized. As a side
809      * effect, it places the externalized SPI String into spiBytes.
810      */
811     private void computeHash() throws ServiceLocationException {
812 	try {
813 	    // get the SPI String bytes
814 	    ByteArrayOutputStream baosT = new ByteArrayOutputStream();
815 	    SrvLocHeader.putStringField(spi, baosT, Defaults.UTF8);
816 	    spiBytes = baosT.toByteArray();
817 	    sig.update(spiBytes);
818 
819 	    // Add each message component
820 	    int mSize = message.length;
821 	    for (int i = 0; i < mSize; i++) {
822 		sig.update((byte[]) message[i]);
823 	    }
824 
825 	    // end by adding the timestamp
826 	    baosT = new ByteArrayOutputStream();
827 	    putInt32(timeStamp, baosT);
828 	    sig.update(baosT.toByteArray());
829 	} catch (SignatureException e) {
830 	    throw new ServiceLocationException(
831 		ServiceLocationException.INTERNAL_SYSTEM_ERROR,
832 		"cant_compute_hash",
833 		new Object[] {e.getMessage()});
834 	}
835     }
836 
837     static private long getInt32(DataInputStream dis) throws IOException {
838 	byte[] bytes = new byte[4];
839 
840 	dis.readFully(bytes, 0, 4);
841 
842 	long a = (long)(bytes[0] & 0xFF);
843 	long b = (long)(bytes[1] & 0xFF);
844 	long c = (long)(bytes[2] & 0xFF);
845 	long d = (long)(bytes[3] & 0xFF);
846 
847 	long i = a << 24;
848 	i += b << 16;
849 	i += c << 8;
850 	i += d;
851 
852 	return i;
853     }
854 
855     static private void putInt32(long i, ByteArrayOutputStream baos) {
856 	baos.write((byte) ((i >> 24) & 0xFF));
857 	baos.write((byte) ((i >> 16) & 0xFF));
858 	baos.write((byte) ((i >> 8)  & 0xFF));
859 	baos.write((byte) (i & 0XFF));
860     }
861 
862     /**
863      * Determines if this process' SPI configuration allows
864      * messages signed by 'cert' to be verified. This method
865      * also verifies and validates 'cert's cert chain.
866      */
867     private void checkSPIs(X509Certificate cert, KeyStore ks)
868 	throws ServiceLocationException, GeneralSecurityException {
869 
870 	// get the list of configured SPIs
871 	String conf_spis = System.getProperty("sun.net.slp.SPIs");
872 	if (conf_spis == null) {
873 	    throw new ServiceLocationException(
874 		ServiceLocationException.AUTHENTICATION_FAILED,
875 		"no_spis_configured", new Object[0]);
876 	}
877 
878 	// Get cert chain
879 	java.security.cert.Certificate[] chain =
880 	    ks.getCertificateChain(cert.getSubjectDN().toString());
881 	if (chain == null) {
882 	    throw new ServiceLocationException(
883 		ServiceLocationException.AUTHENTICATION_FAILED,
884 		"no_cert_chain",
885 		new Object[] {cert.getSubjectDN().toString()});
886 	}
887 
888 	// validate all links in chain
889 	int i = 0;
890 	try {
891 	    // Add cert's own subjec to equiv set
892 	    eqSet.add(((X509Certificate)chain[0]).getSubjectDN());
893 
894 	    for (i = 1; i < chain.length; i++) {
895 		((X509Certificate)chain[i]).checkValidity();
896 		chain[i-1].verify(chain[i].getPublicKey(), "SunAMI");
897 
898 		// OK, so add to equivalency set
899 		eqSet.add(((X509Certificate)chain[i]).getSubjectDN());
900 	    }
901 	} catch (ClassCastException e) {
902 	    throw new ServiceLocationException(
903 		ServiceLocationException.AUTHENTICATION_FAILED,
904 		"not_x509cert",
905 		new Object[] { chain[i].getType(), e.getMessage() });
906 	}
907 
908 	if (configuredToVerify(chain, conf_spis, ks)) {
909 	    return;
910 	}
911 
912 	// if we get here, no SPIs matched, so the authentication fails
913 	throw new ServiceLocationException(
914 		ServiceLocationException.AUTHENTICATION_FAILED,
915 		"cant_match_spis",
916 		new Object[] {cert.getSubjectDN().toString(), ""});
917     }
918 
919     /**
920      * Determines if, given a set of SPIs 'conf_spis', we can
921      * verify a message signed by the Principal named by 'cert'.
922      */
923     static private boolean configuredToVerify(
924 				java.security.cert.Certificate[] chain,
925 				String conf_spis,
926 				KeyStore ks) {
927 
928 	StringTokenizer stk = new StringTokenizer(conf_spis, ",");
929 	while (stk.hasMoreTokens()) {
930 	    String spi;
931 
932 	    try {
933 		spi = stk.nextToken();
934 	    } catch (NoSuchElementException e) {
935 		break;
936 	    }
937 
938 	    // get CA cert to get CA Principal
939 	    Principal ca;
940 	    try {
941 		X509Certificate cacert = getCert(spi, ks);
942 		ca = cacert.getSubjectDN();
943 	    } catch (ServiceLocationException e) {
944 		SLPConfig.getSLPConfig().writeLog(
945 			"cant_process_spi",
946 			new Object[] {spi, e.getMessage()});
947 		continue;
948 	    }
949 
950 	    if (onCertChain(ca, chain)) {
951 		return true;
952 	    }
953 	}
954 
955 	return false;
956     }
957 
958     /**
959      * Determines if sub if equivalent to ca by getting sub's cert
960      * chain and walking the chain looking for ca.
961      * This routine does not verify the cert chain.
962      */
963     private static boolean onCertChain(String sub, Principal ca)
964 	throws ServiceLocationException {
965 
966 	java.security.cert.Certificate[] chain;
967 
968 	ServiceLocationException ex = new ServiceLocationException(
969 			ServiceLocationException.AUTHENTICATION_UNKNOWN,
970 			"no_cert_chain",
971 			new Object[] {sub});
972 
973 	try {
974 	    // Get cert keystore
975 	    KeyStore ks = getKeyStore();
976 
977 	    // Get cert chain for subject
978 	    chain = ks.getCertificateChain(sub);
979 	} catch (KeyStoreException e) {
980 	    throw ex;
981 	}
982 
983 	if (chain == null) {
984 	    throw ex;
985 	}
986 
987 	// walk the cert chain
988 	return onCertChain(ca, chain);
989     }
990 
991     /**
992      * Operates the same as above, but rather than getting the cert
993      * chain for sub, uses a given cert chain.
994      */
995     private static boolean onCertChain(Principal ca,
996 				       java.security.cert.Certificate[] chain)
997     {
998 	// walk the cert chain
999 	for (int i = 0; i < chain.length; i++) {
1000 	    Principal sub = ((X509Certificate)chain[i]).getSubjectDN();
1001 	    if (ca.equals(sub)) {
1002 		return true;
1003 	    }
1004 	}
1005 
1006 	return false;
1007     }
1008 
1009     /**
1010      * Returns true if someDN is in this AuthBlock's equivalence set.
1011      */
1012     private boolean inEqSet(Principal someDN) {
1013 	return eqSet.contains(someDN);
1014     }
1015 
1016     /**
1017      * Retrieves from the KeyStore 'ks' the X509Certificate named
1018      * by DN.
1019      */
1020     static private X509Certificate getCert(String DN, KeyStore ks)
1021 	throws ServiceLocationException {
1022 
1023 	X509Certificate cert = null;
1024 
1025 	// Unescape DN
1026 	try {
1027 	    DN = ServiceLocationAttribute.unescapeAttributeString(DN, false);
1028 	} catch (ServiceLocationException e) {
1029 	    throw new ServiceLocationException(
1030 		ServiceLocationException.PARSE_ERROR,
1031 		"spi_parse_error",
1032 		new Object[] {DN, e.getMessage()});
1033 	}
1034 
1035 	try {
1036 	    cert = (X509Certificate)ks.getCertificate(DN);
1037 	} catch (ClassCastException e) {
1038 	    throw new ServiceLocationException(
1039 		ServiceLocationException.AUTHENTICATION_FAILED,
1040 		"not_x509cert",
1041 		new Object[] {cert.getType(), e.getMessage()});
1042 	} catch (KeyStoreException e) {
1043 	    throw new ServiceLocationException(
1044 		ServiceLocationException.AUTHENTICATION_FAILED,
1045 		"no_cert",
1046 		new Object[] {DN, e.getMessage()});
1047 	}
1048 
1049 	if (cert == null) {
1050 	    throw new ServiceLocationException(
1051 		ServiceLocationException.AUTHENTICATION_FAILED,
1052 		"no_cert",
1053 		new Object[] {DN, "" });
1054 	}
1055 
1056 	return cert;
1057     }
1058 
1059     /**
1060      * Gets a handle to the trusted key package for this process.
1061      */
1062     static private synchronized KeyStore getKeyPkg()
1063 	throws ServiceLocationException {
1064 
1065 	if (keypkg != null) {
1066 	    return keypkg;
1067 	}
1068 
1069 	/* else load key store */
1070 	try {
1071 	    keypkg = KeyStore.getInstance("amiks", "SunAMI");
1072 	    keypkg.load(null, null);
1073 	} catch (Exception e) {
1074 	    throw new ServiceLocationException(
1075 		ServiceLocationException.AUTHENTICATION_FAILED,
1076 		"no_keystore",
1077 		new Object[] {e.getMessage()});
1078 	}
1079 
1080 	return keypkg;
1081     }
1082 
1083     /**
1084      * Gets a handle to a certificate repository.
1085      */
1086     static private synchronized KeyStore getKeyStore()
1087 	throws ServiceLocationException {
1088 
1089 	if (keystore != null) {
1090 	    return keystore;
1091 	}
1092 
1093 	try {
1094 	    keystore = KeyStore.getInstance("amicerts", "SunAMI");
1095 	    keystore.load(null, null);
1096 	} catch (Exception e) {
1097 	    throw
1098 		new ServiceLocationException(
1099 			ServiceLocationException.AUTHENTICATION_FAILED,
1100 			"no_keystore",
1101 			new Object[] {e.getMessage()});
1102 	}
1103 
1104 	return keystore;
1105     }
1106 
1107     public String toString() {
1108 	return  "SPI=``" + spi + "''\n" +
1109 		"                BSD=``" + bsd + "''\n" +
1110 		"                timeStamp=``" + timeStamp + "''\n" +
1111 		"                AuthBlock bytes=" + abLength + " bytes\n";
1112     }
1113 
1114 
1115     // Instance variables
1116     int bsd;
1117     String spi;
1118     Object[] message;
1119     int lifetime;	// need both: lifetime is for optimization,
1120     long timeStamp;	// timeStamp is needed to compute the hash
1121     SrvLocHeader hdr;
1122     Signature sig;
1123     int abLength;
1124     byte[] abBytes;
1125     byte[] spiBytes;
1126     HashSet eqSet;	// built only during authblock verification
1127 
1128     // cached per process
1129     static private KeyStore keystore;	// Certificate repository
1130     static private KeyStore keypkg;	// My own keypkg
1131 }
1132