Source code: net/jxta/impl/membership/pse/PSEMembershipService.java
1 /*
2 * Copyright (c) 2001 Sun Microsystems, Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The end-user documentation included with the redistribution,
17 * if any, must include the following acknowledgment:
18 * "This product includes software developed by the
19 * Sun Microsystems, Inc. for Project JXTA."
20 * Alternately, this acknowledgment may appear in the software itself,
21 * if and wherever such third-party acknowledgments normally appear.
22 *
23 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
24 * must not be used to endorse or promote products derived from this
25 * software without prior written permission. For written
26 * permission, please contact Project JXTA at http://www.jxta.org.
27 *
28 * 5. Products derived from this software may not be called "JXTA",
29 * nor may "JXTA" appear in their name, without prior written
30 * permission of Sun.
31 *
32 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
33 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
35 * DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS OR
36 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
38 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
39 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
40 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
41 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
42 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 * SUCH DAMAGE.
44 * ====================================================================
45 *
46 * This software consists of voluntary contributions made by many
47 * individuals on behalf of Project JXTA. For more
48 * information on Project JXTA, please see
49 * <http://www.jxta.org/>.
50 *
51 * This license is based on the BSD license adopted by the Apache Foundation.
52 *
53 * $Id: PSEMembershipService.java,v 1.13 2004/11/02 00:59:45 bondolo Exp $
54 */
55
56 package net.jxta.impl.membership.pse;
57
58 import java.beans.PropertyChangeListener;
59 import java.beans.PropertyChangeSupport;
60 import java.io.File;
61 import java.net.URI;
62 import java.security.PrivateKey;
63 import java.security.cert.CertPath;
64 import java.security.cert.CertificateFactory;
65 import java.security.cert.X509Certificate;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collections;
69 import java.util.Enumeration;
70 import java.util.Iterator;
71 import java.util.List;
72
73 import java.io.IOException;
74 import java.security.KeyStoreException;
75 import java.security.NoSuchProviderException;
76 import java.security.cert.CertificateException;
77 import java.util.NoSuchElementException;
78
79 import org.apache.log4j.Level;
80 import org.apache.log4j.Logger;
81
82 import net.jxta.credential.AuthenticationCredential;
83 import net.jxta.credential.Credential;
84 import net.jxta.document.Advertisement;
85 import net.jxta.document.AdvertisementFactory;
86 import net.jxta.document.Element;
87 import net.jxta.document.MimeMediaType;
88 import net.jxta.document.StructuredDocumentFactory;
89 import net.jxta.document.StructuredDocumentUtils;
90 import net.jxta.document.XMLDocument;
91 import net.jxta.document.XMLElement;
92 import net.jxta.membership.Authenticator;
93 import net.jxta.membership.MembershipService;
94 import net.jxta.peergroup.PeerGroup;
95 import net.jxta.platform.ModuleSpecID;
96 import net.jxta.protocol.ConfigParams;
97 import net.jxta.protocol.ModuleImplAdvertisement;
98 import net.jxta.protocol.PeerAdvertisement;
99 import net.jxta.service.Service;
100
101 import net.jxta.id.ID;
102
103 import net.jxta.exception.PeerGroupException;
104 import net.jxta.exception.ProtocolNotSupportedException;
105
106 import net.jxta.impl.config.Config;
107 import net.jxta.impl.protocol.Certificate;
108 import net.jxta.impl.protocol.PSEConfigAdv;
109
110 /**
111 * A JXTA Membership Service utilizing PKI to provide secure identities.
112 *
113 * @see net.jxta.membership.MembershipService
114 **/
115 public final class PSEMembershipService implements MembershipService {
116
117 /**
118 * Log4J Logger
119 **/
120 private final static transient Logger LOG = Logger.getLogger(PSEMembershipService.class.getName());
121
122 /**
123 * Well known service specification identifier: pse membership
124 */
125 public final static ModuleSpecID pseMembershipSpecID = (ModuleSpecID) ID.create( URI.create( ID.URIEncodingName + ":" + ID.URNNamespace + ":uuid-DeadBeefDeafBabaFeedBabe000000050306" ) );
126
127 /**
128 * the peergroup to which this service is associated.
129 **/
130 PeerGroup group = null;
131
132 /**
133 * The ID assigned to this instance.
134 **/
135 private ID assignedID = null;
136
137 /**
138 * The ModuleImplAdvertisement which was used to instantiate this service.
139 **/
140 private ModuleImplAdvertisement implAdvertisement = null;
141
142
143 /**
144 * The current set of principals associated with this peer within this peegroup.
145 **/
146 private final List principals;
147
148 /**
149 * The set of AuthenticationCredentials which were used to establish the principals.
150 **/
151 private final List authCredentials;
152
153 /**
154 * property change support
155 **/
156 private final PropertyChangeSupport support;
157
158 /**
159 * the keystore we are working with.
160 **/
161 PSEConfig pseStore = null;
162
163 /**
164 * the default credential
165 **/
166 private PSECredential defaultCredential = null;
167 /**
168 * The configuration we are using.
169 **/
170 private PSEConfigAdv config;
171
172 /**
173 * Default constructor. Normally only called by the peer group.
174 **/
175 public PSEMembershipService() throws PeerGroupException {
176 principals = new ArrayList();
177 authCredentials = new ArrayList();
178 support = new PropertyChangeSupport( getInterface() );
179 }
180
181 /**
182 * @inheritDoc
183 **/
184 public void addPropertyChangeListener( PropertyChangeListener listener ) {
185 support.addPropertyChangeListener(listener );
186 }
187
188 /**
189 * @inheritDoc
190 **/
191 public void addPropertyChangeListener( String propertyName, PropertyChangeListener listener ) {
192 support.addPropertyChangeListener( propertyName, listener );
193 }
194
195 /**
196 * @inheritDoc
197 **/
198 public void removePropertyChangeListener( PropertyChangeListener listener ) {
199 support.removePropertyChangeListener( listener );
200 }
201
202 /**
203 * @inheritDoc
204 **/
205 public void removePropertyChangeListener( String propertyName, PropertyChangeListener listener ) {
206 support.removePropertyChangeListener( propertyName, listener );
207 }
208
209 /**
210 * {@inheritDoc}
211 **/
212 public void init( PeerGroup group, ID assignedID, Advertisement impl ) throws PeerGroupException {
213 this.group = group;
214 this.assignedID = assignedID;
215 this.implAdvertisement = (ModuleImplAdvertisement) impl;
216
217 ConfigParams configAdv = (ConfigParams) group.getConfigAdvertisement();
218
219 // Get our peer-defined parameters in the configAdv
220 Element param = configAdv.getServiceParam(assignedID);
221
222 Advertisement paramsAdv = null;
223 if( null != param ) {
224 try {
225 paramsAdv = AdvertisementFactory.newAdvertisement((XMLElement) param);
226 } catch( NoSuchElementException ignored ) {;}
227
228 if( !(paramsAdv instanceof PSEConfigAdv) ) {
229 throw new PeerGroupException( "Provided Advertisement was not a " + PSEConfigAdv.getAdvertisementType() );
230 }
231
232 config = (PSEConfigAdv) paramsAdv;
233 } else {
234 throw new PeerGroupException( "No configuration information avail from ConfigParams " );
235
236 // config = (PSEConfigAdv) AdvertisementFactory.newAdvertisement( PSEConfigAdv.getAdvertisementType() );
237 //
238 // PSEUtils.IssuerInfo info = PSEUtils.genCert( group.getPeerName(), null);
239 //
240 // config.setCertificate(info.cert);
241 // // FIXME 20041013 bondolo Initialize key.
242 // config.setPrivateKey(info.subjectPkey, "".toCharArray());
243 }
244
245 URI location = config.getKeyStoreLocation();
246 KeyStoreManager store_manager;
247
248 try {
249 if( null == location ) {
250 store_manager = new CMKeyStoreManager(config.getKeyStoreType(), config.getKeyStoreProvider(), group, assignedID);
251 } else {
252 if( !location.isAbsolute() ) {
253 File pseHome = new File( Config.JXTA_HOME );
254 location = location.resolve( pseHome.toURI() );
255 }
256
257 store_manager = new URIKeyStoreManager(config.getKeyStoreType(), config.getKeyStoreProvider(), location);
258 }
259 } catch( NoSuchProviderException not_available ) {
260 throw new PeerGroupException( "Requested KeyStore provider not available", not_available );
261 } catch( KeyStoreException bad ) {
262 throw new PeerGroupException( "KeyStore failure initializing KeyStoreManager", bad );
263 }
264
265 pseStore = new PSEConfig( store_manager, null );
266
267 if (LOG.isEnabledFor(Level.INFO)) {
268 StringBuffer configInfo = new StringBuffer( "Configuring PSE Membership Service : " + assignedID );
269
270 configInfo.append( "\n\tImplementation :" );
271 configInfo.append("\n\t\tModule Spec ID: " + implAdvertisement.getModuleSpecID());
272 configInfo.append("\n\t\tImpl Description : " + implAdvertisement.getDescription());
273 configInfo.append("\n\t\tImpl URI : " + implAdvertisement.getUri());
274 configInfo.append("\n\t\tImpl Code : " + implAdvertisement.getCode());
275
276 configInfo.append( "\n\tGroup Params :" );
277 configInfo.append( "\n\t\tGroup : " + group.getPeerGroupName() );
278 configInfo.append( "\n\t\tGroup ID : " + group.getPeerGroupID() );
279 configInfo.append( "\n\t\tPeer ID : " + group.getPeerID() );
280
281 configInfo.append( "\n\tConfiguration :" );
282 configInfo.append( "\n\t\tPSE state : " + (pseStore.isInitialized( ) ? "inited" : "new") );
283
284 LOG.info( configInfo );
285 }
286
287 resign();
288 }
289
290 /**
291 * {@inheritDoc}
292 **/
293 public Service getInterface() {
294 return this;
295 }
296
297 /**
298 * {@inheritDoc}
299 **/
300 public Advertisement getImplAdvertisement() {
301 return implAdvertisement;
302 }
303
304 /**
305 * {@inheritDoc}
306 *
307 * <p/>Currently this service starts by itself and does not expect
308 * arguments.
309 */
310 public int startApp(String[] arg) {
311
312 return 0;
313 }
314
315 /**
316 * {@inheritDoc}
317 **/
318 public void stopApp() {
319 resign();
320 }
321
322 /**
323 * {@inheritDoc}
324 *
325 * <p/>Supports methods <code>"StringAuthentication"</code>,
326 * <code>"DialogAuthentication"</code> and
327 * <code>"InteractiveAuthentication"</code> (an alias for
328 * <code>"DialogAuthentication"</code>)
329 **/
330 public Authenticator apply( AuthenticationCredential application) throws ProtocolNotSupportedException {
331
332 String method = application.getMethod();
333
334 if( "StringAuthentication".equals( method ) ) {
335 if( pseStore.isInitialized() ) {
336 return new StringAuthenticator( this, application );
337 } else {
338 return new StringAuthenticator( this, application, config.getCertificate(), config.getEncryptedPrivateKey() );
339 }
340 } else if( "DialogAuthentication".equals( method ) || "InteractiveAuthentication".equals( method ) || (null == method) ) {
341 if( pseStore.isInitialized() ) {
342 return new DialogAuthenticator( this, application );
343 } else {
344 return new DialogAuthenticator( this, application, config.getCertificate(), config.getEncryptedPrivateKey() );
345 }
346 } else {
347 throw new ProtocolNotSupportedException( "Authentication method not recognized" );
348 }
349 }
350
351 /**
352 * {@inheritDoc}
353 **/
354 public Credential getDefaultCredential() {
355 return defaultCredential;
356 }
357
358 /**
359 * Sets the default credential. Also updates the peer advertisement with
360 * the certificate of the default credential.
361 *
362 * @param newDefault the new default credential. May also be
363 * <code>null</code> if no default is desired.
364 **/
365 private void setDefaultCredential( PSECredential newDefault ) {
366
367 Credential oldDefault = defaultCredential;
368
369 synchronized( this ) {
370 defaultCredential = newDefault;
371 }
372
373 if (LOG.isEnabledFor(Level.INFO)) {
374 LOG.info( "New Default credential : " + newDefault );
375 }
376
377 try {
378 // include the root cert in the peer advertisement
379 PeerAdvertisement peeradv = group.getPeerAdvertisement();
380
381 if( null != newDefault ) {
382 // include the root cert in the peer advertisement
383 XMLDocument paramDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument( MimeMediaType.XMLUTF8, "Parm");
384
385 Certificate peerCerts = new Certificate();
386 peerCerts.setCertificates( newDefault.getCertificateChain() );
387
388 XMLDocument peerCertsAsDoc = (XMLDocument) peerCerts.getDocument( MimeMediaType.XMLUTF8 );
389
390 StructuredDocumentUtils.copyElements( paramDoc, paramDoc, peerCertsAsDoc, "RootCert" );
391
392 peeradv.putServiceParam( PeerGroup.peerGroupClassID, paramDoc );
393 } else {
394 peeradv.removeServiceParam( PeerGroup.peerGroupClassID );
395 }
396 } catch ( Exception ignored ) {;}
397
398 support.firePropertyChange( "defaultCredential", oldDefault, newDefault );
399 }
400
401 /**
402 * {@inheritDoc}
403 **/
404 public Enumeration getCurrentCredentials() {
405 List credList = Arrays.asList( principals.toArray() );
406
407 return Collections.enumeration(credList);
408 }
409
410 /**
411 * {@inheritDoc}
412 **/
413 public Enumeration getAuthCredentials() {
414 List credList = Arrays.asList( authCredentials.toArray() );
415
416 return Collections.enumeration(credList);
417 }
418
419 /**
420 * {@inheritDoc}
421 **/
422 public Credential join( Authenticator authenticated ) throws PeerGroupException {
423
424 if( this != authenticated.getSourceService() ) {
425 throw new ClassCastException( "This is not my authenticator!" );
426 }
427
428 if( !authenticated.isReadyForJoin() ) {
429 throw new PeerGroupException( "Authenticator not ready to join!" );
430 }
431
432 PSECredential newCred;
433
434 char [] store_password = null;
435 ID identity;
436 char [] key_password = null;
437
438 try {
439 if( authenticated instanceof StringAuthenticator ) {
440 StringAuthenticator auth = (StringAuthenticator) authenticated;
441
442 store_password = auth.getAuth1_KeyStorePassword();
443 identity = auth.getAuth2Identity();
444 key_password = auth.getAuth3_IdentityPassword();
445 } else {
446 if (LOG.isEnabledFor(Level.WARN)) {
447 LOG.warn( "I dont know how to deal with this authenticator " + authenticated );
448 }
449
450 throw new PeerGroupException( "I dont know how to deal with this authenticator" );
451 }
452
453 if( null != store_password ) {
454 pseStore.setKeyStorePassword( store_password );
455 }
456
457 boolean pseInited = pseStore.isInitialized( );
458
459 if ( !pseInited ) {
460 if ( LOG.isEnabledFor(Level.INFO) ) {
461 LOG.info("Creating new PSE key store");
462 }
463
464 try {
465 X509Certificate [] seed_cert = config.getCertificateChain();
466
467 if( null == seed_cert ) {
468 throw new IOException( "Could not read root certificate chain" );
469 }
470
471 PrivateKey seedPrivKey = config.getPrivateKey( key_password );
472
473 if( null == seedPrivKey ) {
474 throw new IOException( "Could not read private key" );
475 }
476
477 pseStore.initialize( );
478
479 pseStore.setKey( identity, seed_cert, seedPrivKey, key_password );
480 } catch ( IOException failed ) {
481 LOG.fatal("Could not create PSE!", failed );
482
483 throw new PeerGroupException( "Could not create PSE!", failed );
484 } catch ( KeyStoreException failed ) {
485 LOG.fatal("Could not create PSE!", failed );
486
487 throw new PeerGroupException( "Could not create PSE!", failed );
488 }
489 }
490
491 try {
492 X509Certificate certList[] = (X509Certificate[]) pseStore.getTrustedCertificateChain( identity );
493
494 if( null == certList ) {
495 certList = new X509Certificate[1];
496
497 certList[0] = pseStore.getTrustedCertificate( identity );
498 }
499
500 PrivateKey privateKey = pseStore.getKey( identity, key_password );
501
502 CertificateFactory cf = CertificateFactory.getInstance( "X.509" );
503
504 CertPath certs = cf.generateCertPath( Arrays.asList( certList ) );
505
506 newCred = new PSECredential( this, identity, certs, privateKey );
507
508 synchronized( this ) {
509 principals.add( newCred );
510
511 authCredentials.add( authenticated.getAuthenticationCredential() );
512 }
513 } catch( IOException failed ) {
514 if (LOG.isEnabledFor(Level.WARN)) {
515 LOG.warn( "Could not create credential.", failed );
516 }
517
518 throw new PeerGroupException( "Could not create credential.", failed );
519 } catch( KeyStoreException failed ) {
520 if (LOG.isEnabledFor(Level.WARN)) {
521 LOG.warn( "Could not create credential.", failed );
522 }
523
524 throw new PeerGroupException( "Could not create credential.", failed );
525 } catch( CertificateException failed ) {
526 if (LOG.isEnabledFor(Level.WARN)) {
527 LOG.warn( "Could not create credential.", failed );
528 }
529
530 throw new PeerGroupException( "Could not create credential.", failed );
531 }
532 } finally {
533 if( null != store_password ) {
534 Arrays.fill( store_password, '\0' );
535 }
536
537 if( null != key_password ) {
538 Arrays.fill( key_password, '\0' );
539 }
540 }
541
542 // XXX bondolo potential but unlikely race condition here.
543 if( null == getDefaultCredential() ) {
544 setDefaultCredential( newCred );
545 }
546
547 support.firePropertyChange( "addCredential", null, newCred );
548
549 return newCred;
550 }
551
552 /**
553 * {@inheritDoc}
554 **/
555 public void resign() {
556 Iterator eachCred = Arrays.asList( principals.toArray() ).iterator();
557
558 synchronized( this ) {
559 principals.clear();
560 authCredentials.clear();
561 }
562
563 setDefaultCredential( null );
564
565 while( eachCred.hasNext() ) {
566 PSECredential aCred = (PSECredential) eachCred.next();
567
568 aCred.setValid( false );
569 }
570 }
571
572 /**
573 * {@inheritDoc}
574 **/
575 public Credential makeCredential(Element element) {
576
577 return new PSECredential( this, element );
578 }
579
580 /**
581 * Returns the key store object associated with this PSE Membership Service.
582 **/
583 public PSEConfig getPSEConfig() {
584 return pseStore;
585 }
586 }