1 package org.apache.maven.plugins.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 java.io.BufferedInputStream;
23 import java.io.BufferedOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.util.zip.ZipEntry;
29 import java.util.zip.ZipInputStream;
30 import java.util.zip.ZipOutputStream;
31
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.codehaus.plexus.util.FileUtils;
34 import org.codehaus.plexus.util.IOUtil;
35 import org.codehaus.plexus.util.StringUtils;
36 import org.codehaus.plexus.util.cli.Commandline;
37
38 /**
39 * Signs a project artifact and attachments using jarsigner.
40 *
41 * @author <a href="cs@schulte.it">Christian Schulte</a>
42 * @version $Id: JarsignerSignMojo.java 802604 2009-08-09 21:09:05Z bentmann $
43 * @goal sign
44 * @phase package
45 * @since 1.0
46 */
47 public class JarsignerSignMojo
48 extends AbstractJarsignerMojo
49 {
50
51 /**
52 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
53 *
54 * @parameter expression="${jarsigner.keystore}"
55 */
56 private String keystore;
57
58 /**
59 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
60 *
61 * @parameter expression="${jarsigner.storepass}"
62 */
63 private String storepass;
64
65 /**
66 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
67 *
68 * @parameter expression="${jarsigner.keypass}"
69 */
70 private String keypass;
71
72 /**
73 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
74 *
75 * @parameter expression="${jarsigner.sigfile}"
76 */
77 private String sigfile;
78
79 /**
80 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
81 *
82 * @parameter expression="${jarsigner.storetype}"
83 */
84 private String storetype;
85
86 /**
87 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
88 *
89 * @parameter expression="${jarsigner.providerName}"
90 */
91 private String providerName;
92
93 /**
94 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
95 *
96 * @parameter expression="${jarsigner.providerClass}"
97 */
98 private String providerClass;
99
100 /**
101 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
102 *
103 * @parameter expression="${jarsigner.providerArg}"
104 */
105 private String providerArg;
106
107 /**
108 * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
109 *
110 * @parameter expression="${jarsigner.alias}"
111 * @required
112 */
113 private String alias;
114
115 /**
116 * Indicates whether existing signatures should be removed from the processed JAR files prior to signing them. If
117 * enabled, the resulting JAR will appear as being signed only once.
118 *
119 * @parameter expression="${jarsigner.removeExistingSignatures}" default-value="false"
120 * @since 1.1
121 */
122 private boolean removeExistingSignatures;
123
124 protected Commandline getCommandline( final File archive, final Commandline commandLine )
125 {
126 if ( archive == null )
127 {
128 throw new NullPointerException( "archive" );
129 }
130 if ( commandLine == null )
131 {
132 throw new NullPointerException( "commandLine" );
133 }
134
135 if ( !StringUtils.isEmpty( this.keystore ) )
136 {
137 commandLine.createArg().setValue( "-keystore" );
138 commandLine.createArg().setValue( this.keystore );
139 }
140 if ( !StringUtils.isEmpty( this.storepass ) )
141 {
142 commandLine.createArg().setValue( "-storepass" );
143 commandLine.createArg().setValue( this.storepass );
144 }
145 if ( !StringUtils.isEmpty( this.keypass ) )
146 {
147 commandLine.createArg().setValue( "-keypass" );
148 commandLine.createArg().setValue( this.keypass );
149 }
150 if ( !StringUtils.isEmpty( this.storetype ) )
151 {
152 commandLine.createArg().setValue( "-storetype" );
153 commandLine.createArg().setValue( this.storetype );
154 }
155 if ( !StringUtils.isEmpty( this.providerName ) )
156 {
157 commandLine.createArg().setValue( "-providerName" );
158 commandLine.createArg().setValue( this.providerName );
159 }
160 if ( !StringUtils.isEmpty( this.providerClass ) )
161 {
162 commandLine.createArg().setValue( "-providerClass" );
163 commandLine.createArg().setValue( this.providerClass );
164 }
165 if ( !StringUtils.isEmpty( this.providerArg ) )
166 {
167 commandLine.createArg().setValue( "-providerArg" );
168 commandLine.createArg().setValue( this.providerArg );
169 }
170 if ( !StringUtils.isEmpty( this.sigfile ) )
171 {
172 commandLine.createArg().setValue( "-sigfile" );
173 commandLine.createArg().setValue( this.sigfile );
174 }
175
176 commandLine.createArg().setFile( archive );
177
178 if ( !StringUtils.isEmpty( this.alias ) )
179 {
180 commandLine.createArg().setValue( this.alias );
181 }
182
183 return commandLine;
184 }
185
186 protected String getCommandlineInfo( final Commandline commandLine )
187 {
188 String commandLineInfo = commandLine != null ? commandLine.toString() : null;
189
190 if ( commandLineInfo != null )
191 {
192 commandLineInfo = StringUtils.replace( commandLineInfo, this.keypass, "'*****'" );
193 commandLineInfo = StringUtils.replace( commandLineInfo, this.storepass, "'*****'" );
194 }
195
196 return commandLineInfo;
197 }
198
199 protected void preProcessArchive( final File archive )
200 throws MojoExecutionException
201 {
202 if ( removeExistingSignatures )
203 {
204 unsignArchive( archive );
205 }
206 }
207
208 /**
209 * Removes any existing signatures from the specified JAR file. We will stream from the input JAR directly to the
210 * output JAR to retain as much metadata from the original JAR as possible.
211 *
212 * @param jarFile The JAR file to unsign, must not be <code>null</code>.
213 * @throws MojoExecutionException If the unsigning failed.
214 */
215 private void unsignArchive( final File jarFile )
216 throws MojoExecutionException
217 {
218 if ( getLog().isDebugEnabled() )
219 {
220 getLog().debug( "Unsigning " + jarFile );
221 }
222
223 File unsignedFile = new File( jarFile.getAbsolutePath() + ".unsigned" );
224
225 ZipInputStream zis = null;
226 ZipOutputStream zos = null;
227 try
228 {
229 zis = new ZipInputStream( new BufferedInputStream( new FileInputStream( jarFile ) ) );
230 zos = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream( unsignedFile ) ) );
231
232 for ( ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry() )
233 {
234 if ( isSignatureFile( ze.getName() ) )
235 {
236 if ( getLog().isDebugEnabled() )
237 {
238 getLog().debug( " Removing " + ze.getName() );
239 }
240
241 continue;
242 }
243
244 zos.putNextEntry( ze );
245
246 IOUtil.copy( zis, zos );
247 }
248
249 }
250 catch ( IOException e )
251 {
252 throw new MojoExecutionException( "Failed to unsign archive " + jarFile + ": " + e.getMessage(), e );
253 }
254 finally
255 {
256 IOUtil.close( zis );
257 IOUtil.close( zos );
258 }
259
260 try
261 {
262 FileUtils.rename( unsignedFile, jarFile );
263 }
264 catch ( IOException e )
265 {
266 throw new MojoExecutionException( "Failed to unsign archive " + jarFile + ": " + e.getMessage(), e );
267 }
268 }
269
270 /**
271 * Checks whether the specified JAR file entry denotes a signature-related file, i.e. matches
272 * <code>META-INF/*.SF</code>, <code>META-INF/*.DSA</code> or <code>META-INF/*.RSA</code>.
273 *
274 * @param entryName The name of the JAR file entry to check, must not be <code>null</code>.
275 * @return <code>true</code> if the entry is related to a signature, <code>false</code> otherwise.
276 */
277 private boolean isSignatureFile( String entryName )
278 {
279 if ( entryName.regionMatches( true, 0, "META-INF", 0, 8 ) )
280 {
281 entryName = entryName.replace( '\\', '/' );
282
283 if ( entryName.indexOf( '/' ) == 8 && entryName.lastIndexOf( '/' ) == 8 )
284 {
285 if ( entryName.regionMatches( true, entryName.length() - 3, ".SF", 0, 3 ) )
286 {
287 return true;
288 }
289 if ( entryName.regionMatches( true, entryName.length() - 4, ".DSA", 0, 4 ) )
290 {
291 return true;
292 }
293 if ( entryName.regionMatches( true, entryName.length() - 4, ".RSA", 0, 4 ) )
294 {
295 return true;
296 }
297 }
298 }
299
300 return false;
301 }
302
303 }