View Javadoc

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 }