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.html 885985 2013-11-09 08:11:31Z 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          try
60          {
61              ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) );
62              try
63              {
64                  return zis.getNextEntry() != null;
65              }
66              finally
67              {
68                  zis.close();
69              }
70          }
71          catch ( Exception e )
72          {
73              // ignore, will fail below
74          }
75  
76          return false;
77      }
78  
79      /**
80       * Removes any existing signatures from the specified JAR file. We will stream from the input JAR directly to the
81       * output JAR to retain as much metadata from the original JAR as possible.
82       *
83       * @param jarFile The JAR file to unsign, must not be <code>null</code>.
84       * @throws java.io.IOException
85       */
86      public static void unsignArchive( File jarFile )
87          throws IOException
88      {
89  
90          File unsignedFile = new File( jarFile.getAbsolutePath() + ".unsigned" );
91  
92          ZipInputStream zis = null;
93          ZipOutputStream zos = null;
94          try
95          {
96              zis = new ZipInputStream( new BufferedInputStream( new FileInputStream( jarFile ) ) );
97              zos = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream( unsignedFile ) ) );
98  
99              for ( ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry() )
100             {
101                 if ( isSignatureFile( ze.getName() ) )
102                 {
103 
104                     continue;
105                 }
106 
107                 zos.putNextEntry(new ZipEntry(ze.getName()));
108 
109                 IOUtil.copy( zis, zos );
110             }
111 
112         }
113         finally
114         {
115             IOUtil.close( zis );
116             IOUtil.close( zos );
117         }
118 
119         FileUtils.rename( unsignedFile, jarFile );
120 
121     }
122 
123     /**
124      * Scans an archive for existing signatures.
125      *
126      * @param jarFile The archive to scan, must not be <code>null</code>.
127      *
128      * @return <code>true</code>, if the archive contains at least one signature file; <code>false</code>, if the
129      * archive does not contain any signature files.
130      *
131      * @throws IOException if scanning <code>jarFile</code> fails.
132      */
133     public static boolean isArchiveSigned( final File jarFile )
134         throws IOException
135     {
136         if ( jarFile == null )
137         {
138             throw new NullPointerException( "jarFile" );
139         }
140 
141         ZipInputStream in = null;
142         boolean suppressExceptionOnClose = true;
143 
144         try
145         {
146             boolean signed = false;
147             in = new ZipInputStream( new BufferedInputStream( new FileInputStream( jarFile ) ) );
148 
149             for ( ZipEntry ze = in.getNextEntry(); ze != null; ze = in.getNextEntry() )
150             {
151                 if ( isSignatureFile( ze.getName() ) )
152                 {
153                     signed = true;
154                     break;
155                 }
156             }
157 
158             suppressExceptionOnClose = false;
159             return signed;
160         }
161         finally
162         {
163             try
164             {
165                 if ( in != null )
166                 {
167                     in.close();
168                 }
169             }
170             catch ( final IOException e )
171             {
172                 if ( !suppressExceptionOnClose )
173                 {
174                     throw e;
175                 }
176             }
177         }
178     }
179 
180     /**
181      * Checks whether the specified JAR file entry denotes a signature-related file, i.e. matches
182      * <code>META-INF/*.SF</code>, <code>META-INF/*.DSA</code> or <code>META-INF/*.RSA</code>.
183      *
184      * @param entryName The name of the JAR file entry to check, must not be <code>null</code>.
185      * @return <code>true</code> if the entry is related to a signature, <code>false</code> otherwise.
186      */
187     private static boolean isSignatureFile( String entryName )
188     {
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                     return true;
198                 }
199                 if ( entryName.regionMatches( true, entryName.length() - 4, ".DSA", 0, 4 ) )
200                 {
201                     return true;
202                 }
203                 if ( entryName.regionMatches( true, entryName.length() - 4, ".RSA", 0, 4 ) )
204                 {
205                     return true;
206                 }
207                 if ( entryName.regionMatches( true, entryName.length() - 3, ".EC", 0, 3 ) )
208                 {
209                     return true;
210                 }
211             }
212         }
213         return false;
214     }
215 }