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 }