All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.appclient.server.core.ApplicationSignedJARManager Maven / Gradle / Ivy

There is a newer version: 6.2024.7
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2014 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.appclient.server.core;

import com.sun.enterprise.deploy.shared.ArchiveFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.glassfish.api.deployment.DeploymentContext;
import org.glassfish.api.deployment.archive.ReadableArchive;
import org.glassfish.appclient.server.core.jws.JavaWebStartInfo;
import org.glassfish.appclient.server.core.jws.servedcontent.ASJarSigner;
import org.glassfish.appclient.server.core.jws.servedcontent.AutoSignedContent;
import org.glassfish.appclient.server.core.jws.servedcontent.FixedContent;
import org.glassfish.appclient.server.core.jws.servedcontent.StaticContent;
import org.glassfish.hk2.api.ServiceLocator;

/**
 * Records information about JARs from an EAR that are used by an
 * app client.  Although JARs can be signed by multiple certificates, this
 * class ultimately associates each JAR with at most one alias with which
 * it was signed.  (This is typically used by the Java Web Start support to
 * group like-signed JARs into the same generated JNLP.  Java Web Start requires
 * that all JARs listed in a single JNLP document be signed by the same cert or
 * be unsigned.)  By organizing the signed JARs by the signing alias used for each,
 * we can easily find all JARs signed by a given alias and then list them
 * in the same generated JNLP.
 * 

* A client class should instantiate the manager, then invoke addJar any number * of times, then invoke aliasToContent to retrieve the map from each alias to * the corresponding (relativeURI, StaticContent) pair. *

* If an added JAR is already signed by the developer we do not sign it again, but * simply add it to the data structures. If an added JAR is not signed we * arrange for it to be auto-signed and the signed version will be the one * served to Java Web Start requests. * * @author tjquinn */ public class ApplicationSignedJARManager { private final Map> relURIToSigningAliases = new HashMap>(); private final Map> signingAliasToRelURIs = new HashMap>(); /** * maps each alias to a map. map entries link the relative URI (used as the * key in the Grizzly adapter's key-to-content map) to the StaticContent * instance for that JAR. In this map each served JAR is associated with * only one alias, even if a JAR is signed by multiple certs. */ private Map> selectedAliasToContentMapping = null; private final ArchiveFactory archiveFactory; private final String autoSigningAlias; private final ASJarSigner jarSigner; private final URI EARDirectoryServerURI; private final DeploymentContext dc; private final AppClientDeployerHelper helper; private final Map relURIToContent = new HashMap(); public ApplicationSignedJARManager( final String autoSigningAlias, final ASJarSigner jarSigner, final ServiceLocator habitat, final DeploymentContext dc, final AppClientDeployerHelper helper, final URI EARDirectoryServerURI, final URI EARDirectoryUserURI) { this.autoSigningAlias = autoSigningAlias; this.jarSigner = jarSigner; this.EARDirectoryServerURI = EARDirectoryServerURI; archiveFactory = habitat.getService(ArchiveFactory.class); this.dc = dc; this.helper = helper; } /** * Adds a JAR to the manager, returning the URI for the file to be served. * The URI within the anchor is derived from the absolute URI relative * to the EAR's anchor on the server. * @param absJARURI absolute URI of the unsigned file. * @return URI to the file to be served * @throws IOException */ public URI addJAR(final URI absJARURI) throws IOException { final URI jarURIRelativeToApp = EARDirectoryServerURI.relativize(absJARURI); return addJAR(jarURIRelativeToApp, absJARURI); } /** * Adds a JAR to the manager, returning the URI to the file to be * served. This might be an auto-signed file if the original JAR is * unsigned. * @param uriWithinAnchor relative URI to the JAR within the anchor directory for the app * @param jarURI URI to the JAR file in the app to be served * @return URI to the JAR file to serve (either the original file or an auto-signed copy of the original) * @throws IOException */ public URI addJAR(final URI uriWithinAnchor, final URI absJARURI) throws IOException { /* * This method accomplishes three things: * * 1. Adds an entry to the map from relative URIs to the corresponding * static content for the JAR, creating an auto-signed content instance * if needed for an unsigned JAR. * * 2. Adds to the map from relative URI to aliases with which the JAR * is signed. * * 3. Adds to the map from alias to relative URIs signed with that alias. */ Map.Entry result; // relative URI -> StaticContent final ReadableArchive arch = archiveFactory.openArchive(absJARURI); final Manifest archiveMF = arch.getManifest(); if (archiveMF == null) { return null; } if ( ! isArchiveSigned(archiveMF)) { /* * The developer did not sign this JARs, so arrange for it to be * auto-signed. */ result = autoSignedAppContentEntry(uriWithinAnchor, absJARURI); updateAliasToURIs(result.getKey(), autoSigningAlias); updateURIToAliases(result.getKey(), autoSigningAlias); } else { /* * The developer did sign this JAR, possibly with many certs. * For each cert add an association between the signing alias and * the JAR. */ result = developerSignedAppContentEntry(absJARURI); Collection aliasesUsedToSignJAR = new ArrayList(); for (Enumeration entryNames = arch.entries("META-INF/"); entryNames.hasMoreElements(); ) { final String entryName = entryNames.nextElement(); final String alias = signatureEntryName(entryName); updateURIToAliases(result.getKey(), alias); } addAliasToURIsEntry(result.getKey(), aliasesUsedToSignJAR); } arch.close(); return result.getKey(); } public Map> aliasToContent() { if (selectedAliasToContentMapping == null) { selectedAliasToContentMapping = pruneMaps(); } return selectedAliasToContentMapping; } private void addAliasToURIsEntry(final URI relURI, final Collection aliases) throws IOException { relURIToSigningAliases.put(relURI, aliases); for (String alias : aliases) { updateAliasToURIs(relURI, alias); } } private void updateURIToAliases(final URI relURI, final String alias) throws IOException { Collection aliasesForJAR = relURIToSigningAliases.get(relURI); if (aliasesForJAR == null) { aliasesForJAR = new ArrayList(); relURIToSigningAliases.put(relURI, aliasesForJAR); } aliasesForJAR.add(alias); } private void updateAliasToURIs(final URI relURI, final String alias) throws IOException { Collection urisForAlias = signingAliasToRelURIs.get(alias); if (urisForAlias == null) { urisForAlias = new ArrayList(); signingAliasToRelURIs.put(alias, urisForAlias); } urisForAlias.add(relURI); } private Map.Entry developerSignedAppContentEntry(URI absURIToFile) { final URI jarURIRelativeToApp = EARDirectoryServerURI.relativize(absURIToFile); StaticContent content = relURIToContent.get(absURIToFile); if (content == null) { content = new FixedContent(new File(absURIToFile)); relURIToContent.put(jarURIRelativeToApp, content); } return new AbstractMap.SimpleEntry( jarURIRelativeToApp, content); } public StaticContent staticContent(final URI jarURIRelativeToApp) { return relURIToContent.get(jarURIRelativeToApp); } /* * Returns information about an auto-signed JAR for a given absolute URI and * alias, creating the auto-signed content object and adding it to the * data structures if it is not already present. */ private synchronized Map.Entry autoSignedAppContentEntry( final URI jarURIRelativeToApp, final URI absURIToFile) throws FileNotFoundException { StaticContent content = relURIToContent.get(jarURIRelativeToApp); if (content == null) { final File unsignedFile = new File(absURIToFile); final File signedFile = signedFileForLib(jarURIRelativeToApp, unsignedFile); content = new AutoSignedContent(unsignedFile, signedFile, autoSigningAlias, jarSigner, jarURIRelativeToApp.toASCIIString(), helper.appName()); relURIToContent.put(jarURIRelativeToApp, content); } else { if (content instanceof AutoSignedContent) { content = AutoSignedContent.class.cast(content); } else { throw new RuntimeException(content.toString() + " != AutoSignedContent"); } } return new AbstractMap.SimpleEntry(jarURIRelativeToApp, content); } private File signedFileForLib(final URI relURI, final File unsignedFile) { return JavaWebStartInfo.signedFileForProvidedAppFile(relURI, unsignedFile, helper, dc); } /** * Returns the signature file name (no path, no suffix) if the specified * entry name matches the pattern of a signature file in a JAR. * @param entryName name to check * @return signature file name; null if the entry name does not match the pattern */ private String signatureEntryName(final String entryName) { final int firstSlash = entryName.indexOf('/'); final int lastSlash = entryName.lastIndexOf('/'); return ((entryName.startsWith("META-INF/") && firstSlash == lastSlash && firstSlash != -1) && (entryName.endsWith(".SF"))) ? entryName.substring(firstSlash + 1, entryName.indexOf(".SF")) : null; } private boolean isArchiveSigned(final Manifest archiveMF) throws IOException { /* * Signature files are *.SF, but looking through all the entries for * ones that match *.SF could be expensive if there are many entries. * Instead check the manifest to * see if it contains per-entry attributes and, if so, if the first * entry has a x-Digest-y entry-level attribute. */ final Map perEntryAttrs = archiveMF.getEntries(); boolean jarIsSigned = false; for (Map.Entry entry : perEntryAttrs.entrySet()) { for (Object attrKey : entry.getValue().keySet()) { if (attrKey.toString().contains("-Digest-") || attrKey.toString().contains("-Digest:")) { jarIsSigned = true; break; } } /* * We need to look only at the first entry because every entry * of a JAR file is recorded as signed in the manifest. */ break; } return jarIsSigned; } private Map> pruneMaps() { /* * We'll eventually generate possibly multiple JNLP documents, one for each * different signing cert and each listing the JARs signed using that cert. * Java Web Start prompts end users for each untrusted cert. that was * used to sign the JARs in a JNLP. During deployment (which is when this code runs) * we cannot tell what certs or trusted authorities might be on an * end-users's system. So we would like to minimize the number of * different JNLPs which might help reduce the number of prompts the * user will see. * * We have a relationship between JARs and signing aliases. Ideally * we'd truly minimize the number of aliases but that's a hard (i.e., * computationally complex) problem and it's unlikely that real apps will contain * JARs signed by large numbers of different certs. So we'll do as * good a job as we can, but quickly. * * If a JAR is signed by exactly one cert then we must include that cert * and we might as well associate any other JARs signed by that cert * and others with that cert. Then we'll process any * unprocessed JARs by choosing for each the alias with which it was * signed with the largest number of other JARs also signed by that * alias. This is not guaranteed to be optimal but it should be * pretty good and will be fast. */ final Set processedJARs = new HashSet(); final Map> selectedAliases = new HashMap>(); for (Map.Entry> entry : relURIToSigningAliases.entrySet()) { if ( ! processedJARs.contains(entry.getKey())) { if (entry.getValue().size() == 1) { processURI(processedJARs, selectedAliases, entry.getKey(), entry.getValue().iterator().next()); } } } /* * We've handled all JARs that have just one signing alias. Now process * any remaining JARs. */ for (Map.Entry> entry : relURIToSigningAliases.entrySet()) { if ( ! processedJARs.contains(entry.getKey())) { processURI(processedJARs, selectedAliases, entry.getKey(), entry.getValue()); } } return selectedAliases; } private void processURI(final Set processedJARs, final Map> selectedAliases, final URI relURI, final String alias) { Map urisForSelectedAlias = selectedAliases.get(alias); if (urisForSelectedAlias == null) { urisForSelectedAlias = new HashMap(); selectedAliases.put(alias, urisForSelectedAlias); } /* * Add this URI to the URIs to be associated with the specified alias. */ urisForSelectedAlias.put(relURI, relURIToContent.get(relURI)); /* * Record that we've processed this URI so we don't do so again. */ processedJARs.add(relURI); /* * Now that we know we need to handle this alias, mark all other JARs * that are associated with this alias (and perhaps others) to be * finally grouped with this alias alone. */ for (URI otherURI : signingAliasToRelURIs.get(alias)) { urisForSelectedAlias.put(otherURI, relURIToContent.get(otherURI)); processedJARs.add(otherURI); } } private void processURI(final Set processedJARs, final Map> selectedAliases, final URI uri, final Collection aliases) { /* * The algorithm we use to choose which of the multiple aliases to use * for this JAR could be anything. We'll just choose the first one. */ processURI(processedJARs, selectedAliases, uri, aliases.iterator().next()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy