View Javadoc

1   package org.apache.maven.shared.jarsigner;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.shared.utils.io.FileUtils;
23  import org.apache.maven.shared.utils.io.IOUtil;
24  
25  import java.io.BufferedInputStream;
26  import java.io.BufferedOutputStream;
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileOutputStream;
30  import java.io.IOException;
31  import java.util.zip.ZipEntry;
32  import java.util.zip.ZipInputStream;
33  import java.util.zip.ZipOutputStream;
34  
35  /**
36   * Useful methods.
37   *
38   * @author tchemit <chemit@codelutin.com>
39   * @version $Id: JarSignerUtil.java 1541101 2013-11-12 15:21:23Z tchemit $
40   * @since 1.0
41   */
42  public class JarSignerUtil
43  {
44  
45      private JarSignerUtil()
46      {
47          // static class
48      }
49  
50      /**
51       * Checks whether the specified file is a JAR file. For our purposes, a ZIP file is a ZIP stream with at least one
52       * entry.
53       *
54       * @param file The file to check, must not be <code>null</code>.
55       * @return <code>true</code> if the file looks like a ZIP file, <code>false</code> otherwise.
56       */
57      public static boolean isZipFile( final File file )
58      {
59          boolean result = false;
60          try
61          {
62              ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) );
63              try
64              {
65                  result = zis.getNextEntry() != null;
66              }
67              finally
68              {
69                  zis.close();
70              }
71          }
72          catch ( Exception e )
73          {
74              // ignore, will fail below
75          }
76  
77          return result;
78      }
79  
80      /**
81       * Removes any existing signatures from the specified JAR file. We will stream from the input JAR directly to the
82       * output JAR to retain as much metadata from the original JAR as possible.
83       *
84       * @param jarFile The JAR file to unsign, must not be <code>null</code>.
85       * @throws java.io.IOException
86       */
87      public static void unsignArchive( File jarFile )
88          throws IOException
89      {
90  
91          File unsignedFile = new File( jarFile.getAbsolutePath() + ".unsigned" );
92  
93          ZipInputStream zis = null;
94          ZipOutputStream zos = null;
95          try
96          {
97              zis = new ZipInputStream( new BufferedInputStream( new FileInputStream( jarFile ) ) );
98              zos = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream( unsignedFile ) ) );
99  
100             for ( ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry() )
101             {
102                 if ( isSignatureFile( ze.getName() ) )
103                 {
104 
105                     continue;
106                 }
107 
108                 zos.putNextEntry( new ZipEntry( ze.getName() ) );
109 
110                 IOUtil.copy( zis, zos );
111             }
112 
113         }
114         finally
115         {
116             IOUtil.close( zis );
117             IOUtil.close( zos );
118         }
119 
120         FileUtils.rename( unsignedFile, jarFile );
121 
122     }
123 
124     /**
125      * Scans an archive for existing signatures.
126      *
127      * @param jarFile The archive to scan, must not be <code>null</code>.
128      * @return <code>true</code>, if the archive contains at least one signature file; <code>false</code>, if the archive
129      *         does not contain any signature files.
130      * @throws IOException if scanning <code>jarFile</code> fails.
131      */
132     public static boolean isArchiveSigned( final File jarFile )
133         throws IOException
134     {
135         if ( jarFile == null )
136         {
137             throw new NullPointerException( "jarFile" );
138         }
139 
140         ZipInputStream in = null;
141         boolean suppressExceptionOnClose = true;
142 
143         try
144         {
145             boolean signed = false;
146             in = new ZipInputStream( new BufferedInputStream( new FileInputStream( jarFile ) ) );
147 
148             for ( ZipEntry ze = in.getNextEntry(); ze != null; ze = in.getNextEntry() )
149             {
150                 if ( isSignatureFile( ze.getName() ) )
151                 {
152                     signed = true;
153                     break;
154                 }
155             }
156 
157             suppressExceptionOnClose = false;
158             return signed;
159         }
160         finally
161         {
162             try
163             {
164                 if ( in != null )
165                 {
166                     in.close();
167                 }
168             }
169             catch ( IOException e )
170             {
171                 if ( !suppressExceptionOnClose )
172                 {
173                     throw e;
174                 }
175             }
176         }
177     }
178 
179     /**
180      * Checks whether the specified JAR file entry denotes a signature-related file, i.e. matches
181      * <code>META-INF/*.SF</code>, <code>META-INF/*.DSA</code> or <code>META-INF/*.RSA</code>.
182      *
183      * @param entryName The name of the JAR file entry to check, must not be <code>null</code>.
184      * @return <code>true</code> if the entry is related to a signature, <code>false</code> otherwise.
185      */
186     private static boolean isSignatureFile( String entryName )
187     {
188         boolean result = false;
189         if ( entryName.regionMatches( true, 0, "META-INF", 0, 8 ) )
190         {
191             entryName = entryName.replace( '\\', '/' );
192 
193             if ( entryName.indexOf( '/' ) == 8 && entryName.lastIndexOf( '/' ) == 8 )
194             {
195                 if ( entryName.regionMatches( true, entryName.length() - 3, ".SF", 0, 3 ) )
196                 {
197                     result = true;
198                 }
199                 else if ( entryName.regionMatches( true, entryName.length() - 4, ".DSA", 0, 4 ) )
200                 {
201                     result = true;
202                 }
203                 else if ( entryName.regionMatches( true, entryName.length() - 4, ".RSA", 0, 4 ) )
204                 {
205                     result = true;
206                 }
207                 else if ( entryName.regionMatches( true, entryName.length() - 3, ".EC", 0, 3 ) )
208                 {
209                     result = true;
210                 }
211             }
212         }
213         return result;
214     }
215 }