/* * Copyright (c) 2001-2005 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by Sun Microsystems, Inc. for JXTA(TM) technology." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must * not be used to endorse or promote products derived from this software * without prior written permission. For written permission, please contact * Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", nor may * "JXTA" appear in their name, without prior written permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * This software consists of voluntary contributions made by many individuals * on behalf of Project JXTA. For more information on Project JXTA, please see * http://www.jxta.org. * * This license is based on the BSD license adopted by the Apache Foundation. */ package tutorial.psesample; import java.io.ByteArrayInputStream; import java.io.FileReader; import java.io.IOException; import java.io.StringReader; import java.net.URI; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Map; import javax.crypto.EncryptedPrivateKeyInfo; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import net.jxta.discovery.DiscoveryService; import net.jxta.document.AdvertisementFactory; import net.jxta.document.Element; import net.jxta.document.MimeMediaType; import net.jxta.document.StructuredDocumentFactory; import net.jxta.document.XMLDocument; import net.jxta.id.ID; import net.jxta.peergroup.PeerGroup; import net.jxta.peergroup.NetPeerGroupFactory; import net.jxta.peergroup.PeerGroupID; import net.jxta.platform.ModuleSpecID; import net.jxta.protocol.ModuleImplAdvertisement; import net.jxta.protocol.PeerGroupAdvertisement; import net.jxta.impl.membership.pse.PSEMembershipService; import net.jxta.impl.membership.pse.PSEUtils; import net.jxta.impl.peergroup.StdPeerGroupParamAdv; import net.jxta.impl.protocol.PSEConfigAdv; /** * A sample program which demonstrates usage of the JXTA PSE Membership Service. * <p/> * <p/>This sample is focused upon secure management of certificate chains, * certificate signing and identity management. It implements a peergroup which * provides four types of users: * <ul> * <li>Owner</li> * <li>Administrator</li> * <li>Member</li> * <li>Invitee</li> * <li>Interloper (not valid)</li> * </ul> * <p/> * <p/>Each of these user types has certain abilities within the peergroup. * <p/> * <dl> * <dt>Owner</dt> * <dd>Peer Group Owners have all rights within the group and are the owner * of the private key for the group's root certificate. Peers may become the * Peer Group Owner by knowing the password used to encrypt the peer group's * private key. The Peer Group Owner may designate peers as Administrators by * signing their certificate with the peer group private key.</dd> * <p/> * <dt>Administrator<dt> * <dd>Peer Group Administrators are peers designated by the Peer Group * Owners to have rights beyond that of regular Member peers. Administrator * peers may generate peer group invitations and may validate Member peers. * Administrator peers have their certifcate signed with the peer group owner * private key. Administrators can sign the certificate of Peer Group Invitees * to allow them to become Peer Group Members. Administrators can generate * Peer Group invitations which can be used by peers to join the peer group. * A Peer Group invitation contains a certificate signed with the * Administrator's private key and the matching private. The private key is * encryped with a password. To use the invitation a peer must know the * password for the invitations private key.</dd> * <p/> * <dt>Member</dt> * <dd>Peer Group Members are the standard user class for this sample. Peer * Group Members have a certificate that is signed with the private key of a * Peer Group Administrator (which in turn is signed with the private key of a * Peer Group Owner). Peer Group Members can request their certificate to be * signed by a Peer Group Owner in order to become a Peer Group Administrator. * <dd> * <p/> * <dt>Invitee</dt> * <dd>Peer Group Invitees are peers who's certificate is signed by an * invitation certificate. Invitees sign their own certificate with the private * key from the Peer Group invitation they used when first joining the peer * group. Peer Group Invitees can request that their certificate be signed by * a Peer Group Adminstrator in order to become a Peer Group Member.</dd> * <p/> * <dt>Interloper</dt> * <dd>Peer Group Interlopers are unauthorized peers who do not have a * correctly signed certificate. Interloper peers may attempt to interact with * other peers within the peer group, but all of their requests will be * ignored without the correct certificates. While potentially annoying Peer * Group Interloper peers are easily detected and can do no harm within the * peer group.</dd> * </dl> * * @see net.jxta.membership.MembershipService * @see net.jxta.impl.membership.pse.PSEMembershipService */ public class Main { /** * If true then the net peer group will be started which causes the * JXTA Shell to be started. This is useful for debugging. */ private final static boolean START_NETGROUP = true; /** * The PeerGroup ID used by this example. We use a constant ID so that * every peer running this test program runs within in the same group. * A real application would need to support more than one peergroup and * should use discovery to locate appropriage peer groups. * <p/> * <p/>This ID was created by making a new group adv in the JXTA shell * with <tt>newpgrp</tt> and then copying the ID from the advertisement. */ private final static PeerGroupID PSE_SAMPLE_PGID = (PeerGroupID) ID.create( URI.create("urn:jxta:uuid-6E0C1C2781794A2F983EA4D2ACB758E602")); /** * The ModuleSpec ID used for this example's peer group. We use a * constant ID so that every peer which creates peer groups uses the * same Module Spec ID. * <p/> * <p/>This ID was created by making a new group adv in the JXTA shell * with <tt>newpgrp</tt> and then copying the ID from the advertisement. */ private final static ModuleSpecID PSE_SAMPLE_MSID = (ModuleSpecID) ID.create( URI.create("urn:jxta:uuid-DEADBEEFDEAFBABAFEEDBABE0000000133BF5414AC624CC8AD3AF6AEC2C8264306")); /** * This Certificate is the root certificate for the sample PSE peer group. All important * certificate chains in the group have this certificate as their root. Since * every peer in the group is supposed to know this cert sometimes it's * omitted from chains if the last certificate in the chain is signed by * this certificate. * <p/> * <p/>The certificate (and the private key) were created using the JXTA * Shell with the following command: * <p/> * <pre> * JXTA><b>pse.createkey PSE_Sample_Root urn:jxta:uuid-6E0C1C2781794A2F983EA4D2ACB758E602 topsecret</b> * JXTA><b>psecred = login</b> * peer - Enter the identity you want to use for group 'NetPeerGroup' : * KeyStorePassword : <b>password</b> * Identity : <b>urn:jxta:uuid-6E0C1C2781794A2F983EA4D2ACB758E602</b> * IdentityPassword : <b>topsecret</b> * JXTA><b>pse.dumpcred -a psecred</b> * </pre> * <p/> * <p/>Certificate presented here has been BASE64ed for easier inclusion. * <p/> * <p/>When stored in the PSE keystore this certificate will be stored * using the Peer Group ID as it's ID. */ private final static String PSE_SAMPLE_GROUP_ROOT_CERT_BASE64 = "MIICGTCCAYKgAwIBAgIBATANBgkqhkiG9w0BAQUFADBTMRUwEwYDVQQKEwx3d3cuanh0YS5vcmcx" + "GzAZBgNVBAMTElBTRV9TYW1wbGVfUm9vdC1DQTEdMBsGA1UECxMURERFMkNGQ0MyMjVBNDM0OUQx" + "QTAwHhcNMDUwNjE5MDIyODM4WhcNMTUwNjE5MDIyODM4WjBTMRUwEwYDVQQKEwx3d3cuanh0YS5v" + "cmcxGzAZBgNVBAMTElBTRV9TYW1wbGVfUm9vdC1DQTEdMBsGA1UECxMURERFMkNGQ0MyMjVBNDM0" + "OUQxQTAwgZ4wDQYJKoZIhvcNAQEBBQADgYwAMIGIAoGAdVgeJotJWEEfh/NtusfI8cAIMAq7WxXA" + "ZsPIOYnybHPXFNmCTozs/KW0dx01zI6kfHwO1qYXmR/djJAFhr3VhFdUp8y1wDCf6DT63vFOi47t" + "6TC1yywjZe59VIAxhDt0B8XJnkEbsEl+uO95ec6/U6dYI1vrtWU4ORdSYz615XMCAwEAATANBgkq" + "hkiG9w0BAQUFAAOBgQBRJXLRyIGHvw3GJC3lYwQUDwRSm6vaPKPlCA5Axfwy+jPuStldhuPYOvxz" + "a3NxQ/iBlzTGwoVzgxzArM6oLRvtAAvvkQl8z6Lu+NF2ugMs6XfuzRKqrBvSjNaSYM83E51niga2" + "3UGc4Brbn3RCTPRADykVhWxgiCADNGVBIBUAMw=="; /** * Private key for the root certificate of the sample PSE peer group. <b>In * most applications the private key would not be included as part of the * source code</b> For this sample to be entirely standalone it must be * included.</b> * <p/> * <p/>The certificate (and the private key) were created using the JXTA * Shell with the following commands: * <p/> * <pre> * <i>... commands shown in comment of "PSE_SAMPLE_GROUP_ROOT_CERT" ...</i> * JXTA><b>pse.dumpcred -a -p -x topsecret psecred</b> * </pre> * <p/> * <p/>The private key has been protected using PKCS#8 password based * encryption. The password for the encrypted private key is the string * "<tt>topsecret</tt>". * <p/> * <p/>As you use this sample you will see that only one peer is required * to know the root key. * <p/> * <p/>The key presented here has been encrypted using PKCS#5 PBKD, wrapped * as a PKCS#8 encrpyted key and then finally BASE64ed for easier inclusion. * <p/> * <p/>When stored in the PSE keystore this key will be stored using the * Peer Group ID as it's ID. */ private final static String PSE_SAMPLE_GROUP_ROOT_ENCRYPTED_KEY_BASE64 = "MIICoTAbBgkqhkiG9w0BBQMwDgQIPPpnqsvvaS0CAicQBIICgJAYTLxQfaUMFL08DnrO/tAZioTu" + "TlUnt32h3n9nE/L0UM8u7Q9elq2YwBNN72LD6ODzZKPmS/PnUl0NnE1AOnLVuMUgl1OBXgmUtC4P" + "jfgA+En7S0YEmgZN42ceqMpcKGDiBNdr0ebGD9SVy4/XkTLrNcEcHqrhyC6JkSOAo2EKL9OkS6gR" + "bVp59JSEiAruDvAZnz3XTjlXJYchZGcMVNfCDJVEMCgCsaKkr1Pf5JAfj1kKBJbazwlvqVrU0eI7" + "nPXTdTNVUaZLA7ucbUialef2/osefm5oB00DVkgIkUQSjesVM+THKu3UxIFe+3yTbUsI3zDja+DK" + "36l+UBmCLwFSOzJ1HAzP2qj1yvE/crEsvZMr9QrfNp7acfZQCgJZWFBG0wkdkvpTC0SBbzD6TqdW" + "hbGq8rca4KDkI4HeVoB3yBnMDm52NOtvh2uTKHul7Zz+3GTjXTIT7B4WcdiKmYo5hzdAidHzrWHV" + "eTmBnda34kM4o0uX1rQjWe3pfpp7rKG/zRDMUsqaZhK0k3t8IiNZroMnH39wz/kiRWgh+LBZOmi6" + "vG4LeaNDom6+o1tH4lHFXh0uCOSjOOKvX91BaptgXXLuFpny1ZMPnSkWzZA20nCJgNB1+S5RLQGg" + "jObczNUFtI8c/nSlbn339fN9G9/EpGaQuoMqxoSWwVnMnfmBnYlq2LehZ3UC3DgSaxRI9XN/F2Ul" + "ako4dwiccGcMsGHB/eKHQU/Csk9E19GGghwC2L7Tb2zIx01Ctd2yecpK3clhvN35xR5cvtnKKLtA" + "KSi8v6rCLDJ0cPa88QfIHRk+M5ZTDP5QN4A0uFKnsWtMI/xjA9tK4VsMEMtxqjBFem8="; /** * Root certificate of the sample PSE peer group. */ final static X509Certificate PSE_SAMPLE_GROUP_ROOT_CERT; /** * Private key for the root certificate of the sample PSE peer group. */ private final static EncryptedPrivateKeyInfo PSE_SAMPLE_GROUP_ROOT_ENCRYPTED_KEY; static { /* Initialize some static final variables */ try { // Initialize the Root certificate. byte[] cert_der = PSEUtils.base64Decode(new StringReader(PSE_SAMPLE_GROUP_ROOT_CERT_BASE64)); CertificateFactory cf = CertificateFactory.getInstance("X509"); // Initialize the Root private key. PSE_SAMPLE_GROUP_ROOT_CERT = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cert_der)); byte[] key_der = PSEUtils.base64Decode(new StringReader(PSE_SAMPLE_GROUP_ROOT_ENCRYPTED_KEY_BASE64)); PSE_SAMPLE_GROUP_ROOT_ENCRYPTED_KEY = new EncryptedPrivateKeyInfo(key_der); } catch (IOException failure) { IllegalStateException failed = new IllegalStateException("Could not read certificate or key."); failed.initCause(failure); throw failed; } catch (CertificateException failure) { IllegalStateException failed = new IllegalStateException("Could not process certificate."); failed.initCause(failure); throw failed; } } /** * Creates a new instance of Main */ public Main() throws Exception { // Start JXTA, instantiate the net peer group. NetPeerGroupFactory npgf = new NetPeerGroupFactory(); final PeerGroup npg = npgf.getInterface(); // This will cause the JXTA Shell to start if enabled. if (START_NETGROUP) { npg.startApp(new String[0]); } // Build the Module Impl Advertisemet we will use for our group. ModuleImplAdvertisement pseImpl = build_psegroup_impl_adv(npg); // Publish the Module Impl Advertisement to the group where the // peergroup will be advertised. This should be done in every peer // group in which the Peer Group is also advertised. // We use the same expiration and lifetime that the Peer Group Adv // will use (the default). DiscoveryService disco = npg.getDiscoveryService(); disco.publish(pseImpl, PeerGroup.DEFAULT_LIFETIME, PeerGroup.DEFAULT_EXPIRATION); PeerGroupAdvertisement pse_pga = null; // Options for the Option Pane Dialog final Object[] options = { "Create self-invitation", "Use invitation..." }; while (null == pse_pga) { /* We use a JOptionPane to quickly choose between the choices * available for starting the PSE Sample Peer Group. * * The choices are: * - Create a self-invitation * - Use an invitation from a file * * "Self Invite" - If you know the password for the hard coded * root certificate/private key pair then you can create a group * advertisement which enables you to create and instantiate the * peer group with owner privleges. Only one peer ever needs to do * this as the res of the peers can join using generated invitations. * * "Use Invitation" - Use a peer group advertisement provided by * another peer in the group in order to join the peergroup. */ int optionPicked = JOptionPane.showOptionDialog(null, "To start the PSE Sample Peer Group you must choose a source for the Peer Group Advertisement.", "Start PSE Sample Peer Group", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); if (JOptionPane.CLOSED_OPTION == optionPicked) { // User cancelled dialog. They want to quit. break; } switch (optionPicked) { case 0: // Create Self-Invitation. X509Certificate[] invitationCertChain = {PSE_SAMPLE_GROUP_ROOT_CERT}; pse_pga = build_psegroup_adv(pseImpl, invitationCertChain, PSE_SAMPLE_GROUP_ROOT_ENCRYPTED_KEY); break; case 1: default: // Use an invitation from a file. final JFileChooser fc = new JFileChooser(); // In response to a button click: int returnVal = fc.showOpenDialog(null); if (returnVal == JFileChooser.APPROVE_OPTION) { FileReader invitation = new FileReader(fc.getSelectedFile()); XMLDocument advDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, invitation); pse_pga = (PeerGroupAdvertisement) AdvertisementFactory.newAdvertisement(advDoc); invitation.close(); } break; } } // If we have a Peer Group Advertisement then instantiate the group // and run the main application. if (null != pse_pga) { // Publish the Peer Group. This will also be done when the group is // instantiated, but we do it here because we want to ensure // what lifetime and expiration to use. disco.publish(pse_pga, PeerGroup.DEFAULT_LIFETIME, PeerGroup.DEFAULT_EXPIRATION); // The invokeLater inner class needs it to be "final". So dupe it. final PeerGroupAdvertisement ui_pga = pse_pga; // `Invoke the Swing based user interface. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { new tutorial.psesample.SwingUI(npg, ui_pga).setVisible(true); } }); } } /** * Start of the PSE peer group sample. * * @param args the command line arguments. Unused by this sample. */ public static void main(String[] args) { // Give the Thread we are running on a name for debuggers. Thread.currentThread().setName(Main.class.getName() + ".main()"); try { new Main(); } catch (Throwable all) { // Something bad happened, print out the failure and quit. System.err.println("Uncaught Throwable in main() :"); all.printStackTrace(System.err); System.exit(1); } } /** * Build a Module Implementation Advertisement suitable for the PSE Sample * Peer Group. The <tt>ModuleImplAdvertisement</tt> is built using the * result of <tt>base.getAllPurposePeerGroupImplAdvertisement()</tt> to * ensure that the result will be appropriate for running as a child * peer group of <tt>base</tt>. * <p/> * <p/>The default advertisement is modified to use the PSE Membership * Service as it's membership service replacing whatever membership * service was originally specified. * <p/> * <p/>The Module Spec ID of the ModuleImplAdvertisement is set to the * hard-coded id <tt>PSE_SAMPLE_MSID</tt> so that all peer group instances * have a consistent (and compatible) specification. * * @param base The Peer Group from which we will retrieve the default * Module Implementation Advertisement. * @return The Module Implementation Advertisement for the PSE Sample * Peer Group. */ static ModuleImplAdvertisement build_psegroup_impl_adv(PeerGroup base) { ModuleImplAdvertisement newGroupImpl; try { newGroupImpl = base.getAllPurposePeerGroupImplAdvertisement(); } catch (Exception unlikely) { // getAllPurposePeerGroupImplAdvertisement() doesn't really throw expections. throw new IllegalStateException("Could not get All Purpose Peer Group Impl Advertisement."); } newGroupImpl.setModuleSpecID(PSE_SAMPLE_MSID); newGroupImpl.setDescription("PSE Sample Peer Group Implementation"); // FIXME bondolo Use something else to edit the params. StdPeerGroupParamAdv params = new StdPeerGroupParamAdv(newGroupImpl.getParam()); Map services = params.getServices(); ModuleImplAdvertisement aModuleAdv = (ModuleImplAdvertisement) services.get(PeerGroup.membershipClassID); services.remove(PeerGroup.membershipClassID); ModuleImplAdvertisement implAdv = (ModuleImplAdvertisement) AdvertisementFactory.newAdvertisement( ModuleImplAdvertisement.getAdvertisementType()); implAdv.setModuleSpecID(PSEMembershipService.pseMembershipSpecID); implAdv.setCompat(aModuleAdv.getCompat()); implAdv.setCode(PSEMembershipService.class.getName()); implAdv.setUri(aModuleAdv.getUri()); implAdv.setProvider(aModuleAdv.getProvider()); implAdv.setDescription("PSE Membership Service"); // Add our selected membership service to the peer group service as the // group's default membership service. services.put(PeerGroup.membershipClassID, implAdv); // Save the group impl parameters newGroupImpl.setParam((Element) params.getDocument(MimeMediaType.XMLUTF8)); return newGroupImpl; } /** * Build the Peer Group Advertisement for the PSE Sample Peer Group. * <p/> * <p/>The Peer Group Advertisement will be generated to contain an * invitation certificate chain and encrypted private key. Peers which * know the password for the Peer Group Root Certificate Key can generate * their own invitation otherwise peers must get an invitation from * another group member. * <p/> * <p/>The invitation certificate chain appears in two forms: * <ul> * <li>Self Invitation : PSE Sample Group Root Certificate + Encrypted Private Key</li> * <li>Regular Invitation : * <ul> * <li>Invitation Certificate + Encrpyted Private Key</li> * <li>Peer Group Member Certificate</li> * <li>Peer Group Administrator Certificate</li> * <li>PSE Sample Group Root Certificate</li> * </ul></li> * </ul> * <p/> * <p/>Invitations are provided to prospective peer group members. You can * use a unique invitation for each prospective member or a single * static invitation for every prospective member. If you use a static * invitation certificate keep in mind that every copy will use the same * shared password and thus the invitation will provide only very limited * security. * <p/> * <p/>In some applications the invitation password will be built in to the * application and the human user will never have to know of it's use. * This can be useful if you wish your PSE Peer Group used only by a single * application. * * @param pseImpl The Module Impl Advertisement which the Peer Group * Advertisement will reference for its Module Spec ID. * @param invitationCertChain The certificate chain which comprises the * PeerGroup Invitation. * @param invitationPrivateKey The private key of the invitation. * @return The Peer Group Advertisement. */ static PeerGroupAdvertisement build_psegroup_adv(ModuleImplAdvertisement pseImpl, X509Certificate[] invitationCertChain, EncryptedPrivateKeyInfo invitationPrivateKey) { PeerGroupAdvertisement newPGAdv = (PeerGroupAdvertisement) AdvertisementFactory.newAdvertisement( PeerGroupAdvertisement.getAdvertisementType()); newPGAdv.setPeerGroupID(PSE_SAMPLE_PGID); newPGAdv.setModuleSpecID(pseImpl.getModuleSpecID()); newPGAdv.setName("PSE Sample Peer Group"); newPGAdv.setDescription("Created by PSE Sample!"); PSEConfigAdv pseConf = (PSEConfigAdv) AdvertisementFactory.newAdvertisement(PSEConfigAdv.getAdvertisementType()); pseConf.setCertificateChain(invitationCertChain); pseConf.setEncryptedPrivateKey(invitationPrivateKey, invitationCertChain[0].getPublicKey().getAlgorithm()); XMLDocument pseDoc = (XMLDocument) pseConf.getDocument(MimeMediaType.XMLUTF8); newPGAdv.putServiceParam(PeerGroup.membershipClassID, pseDoc); return newPGAdv; } }