View Javadoc
1   package org.apache.maven.plugin.gpg;
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.BufferedReader;
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStreamReader;
26  import java.lang.reflect.InvocationTargetException;
27  import java.lang.reflect.Method;
28  import java.util.List;
29  
30  import org.apache.maven.plugin.MojoExecutionException;
31  import org.apache.maven.plugin.logging.Log;
32  import org.apache.maven.project.MavenProject;
33  
34  /**
35   * A base class for all classes that implements signing of files.
36   *
37   * @author Dennis Lundberg
38   * @since 1.5
39   */
40  public abstract class AbstractGpgSigner
41  {
42      public static final String SIGNATURE_EXTENSION = ".asc";
43  
44      protected boolean useAgent;
45  
46      protected boolean isInteractive = true;
47  
48      protected boolean defaultKeyring = true;
49  
50      protected String keyname;
51  
52      private Log log;
53  
54      protected String passphrase;
55  
56      private File outputDir;
57  
58      private File buildDir;
59  
60      private File baseDir;
61  
62      protected File homeDir;
63  
64      protected String secretKeyring;
65  
66      protected String publicKeyring;
67  
68      protected String lockMode;
69  
70      protected List<String> args;
71  
72      public Log getLog()
73      {
74          return log;
75      }
76  
77      public void setArgs( List<String> args )
78      {
79          this.args = args;
80      }
81  
82      public void setInteractive( boolean b )
83      {
84          isInteractive = b;
85      }
86  
87      public void setLockMode( String lockMode )
88      {
89          this.lockMode = lockMode;
90      }
91  
92      public void setUseAgent( boolean b )
93      {
94          useAgent = b;
95      }
96  
97      public void setDefaultKeyring( boolean enabled )
98      {
99          defaultKeyring = enabled;
100     }
101 
102     public void setKeyName( String s )
103     {
104         keyname = s;
105     }
106 
107     public void setLog( Log log )
108     {
109         this.log = log;
110     }
111 
112     public void setPassPhrase( String s )
113     {
114         passphrase = s;
115     }
116 
117     public void setOutputDirectory( File out )
118     {
119         outputDir = out;
120     }
121 
122     public void setBuildDirectory( File out )
123     {
124         buildDir = out;
125     }
126 
127     public void setBaseDirectory( File out )
128     {
129         baseDir = out;
130     }
131 
132     public void setHomeDirectory( File homeDirectory )
133     {
134         homeDir = homeDirectory;
135     }
136 
137     public void setSecretKeyring( String path )
138     {
139         secretKeyring = path;
140     }
141 
142     public void setPublicKeyring( String path )
143     {
144         publicKeyring = path;
145     }
146 
147     /**
148      * Create a detached signature file for the provided file.
149      *
150      * @param file The file to sign
151      * @return A reference to the generated signature file
152      * @throws org.apache.maven.plugin.MojoExecutionException
153      */
154     public File generateSignatureForArtifact( File file )
155         throws MojoExecutionException
156     {
157         // ----------------------------------------------------------------------------
158         // Set up the file and directory for the signature file
159         // ----------------------------------------------------------------------------
160 
161         File signature = new File( file + SIGNATURE_EXTENSION );
162 
163         boolean isInBuildDir = false;
164         if ( buildDir != null )
165         {
166             File parent = signature.getParentFile();
167             if ( buildDir.equals( parent ) )
168             {
169                 isInBuildDir = true;
170             }
171         }
172         if ( !isInBuildDir && outputDir != null )
173         {
174             String fileDirectory = "";
175             File signatureDirectory = signature;
176 
177             while ( ( signatureDirectory = signatureDirectory.getParentFile() ) != null )
178             {
179                 if ( !signatureDirectory.equals( baseDir ) )
180                 {
181                     fileDirectory = signatureDirectory.getName() + File.separatorChar + fileDirectory;
182                 }
183                 else
184                 {
185                     break;
186                 }
187             }
188             signatureDirectory = new File( outputDir, fileDirectory );
189             if ( !signatureDirectory.exists() )
190             {
191                 signatureDirectory.mkdirs();
192             }
193             signature = new File( signatureDirectory, file.getName() + SIGNATURE_EXTENSION );
194         }
195 
196         if ( signature.exists() )
197         {
198             signature.delete();
199         }
200 
201         // ----------------------------------------------------------------------------
202         // Generate the signature file
203         // ----------------------------------------------------------------------------
204 
205         generateSignatureForFile( file, signature );
206 
207         return signature;
208     }
209 
210     /**
211      * Generate the detached signature file for the provided file.
212      *
213      * @param file The file to sign
214      * @param signature The file in which the generate signature will be put
215      * @throws MojoExecutionException
216      */
217     protected abstract void generateSignatureForFile( File file, File signature )
218         throws MojoExecutionException;
219 
220     private MavenProject findReactorProject( MavenProject prj )
221     {
222         if ( prj.getParent() != null && prj.getParent().getBasedir() != null && prj.getParent().getBasedir().exists() )
223         {
224             return findReactorProject( prj.getParent() );
225         }
226         return prj;
227     }
228 
229     public String getPassphrase( MavenProject project )
230         throws IOException
231     {
232         String pass = null;
233 
234         if ( project != null )
235         {
236             pass = project.getProperties().getProperty( "gpg.passphrase" );
237             if ( pass == null )
238             {
239                 MavenProject prj2 = findReactorProject( project );
240                 pass = prj2.getProperties().getProperty( "gpg.passphrase" );
241             }
242         }
243         if ( pass == null )
244         {
245             pass = readPassword( "GPG Passphrase: " );
246         }
247         if ( project != null )
248         {
249             findReactorProject( project ).getProperties().setProperty( "gpg.passphrase", pass );
250         }
251         return pass;
252     }
253 
254     private String readPassword( String prompt )
255         throws IOException
256     {
257         try
258         {
259             return readPasswordJava16( prompt );
260         }
261         catch ( IOException e )
262         {
263             throw e;
264         }
265         catch ( NoSuchMethodException e )
266         {
267             return readPasswordJava15( prompt );
268         }
269         catch ( IllegalAccessException e )
270         {
271             return readPasswordJava15( prompt );
272         }
273         catch ( InvocationTargetException e )
274         {
275             return readPasswordJava15( prompt );
276         }
277     }
278 
279     private String readPasswordJava16( String prompt )
280         throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
281     {
282         Method consoleMethod = System.class.getMethod( "console" );
283         Object console = consoleMethod.invoke( null );
284         if ( console == null )
285         {
286             throw new IllegalAccessException( "console was null" );
287         }
288         Method readPasswordMethod = console.getClass().getMethod( "readPassword", String.class, Object[].class );
289         return new String( (char[]) readPasswordMethod.invoke( console, prompt, null ) );
290     }
291 
292     private String readPasswordJava15( String prompt )
293         throws IOException
294     {
295         BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) );
296         while ( System.in.available() != 0 )
297         {
298             // there's some junk already on the input stream, consume it
299             // so we can get the real passphrase
300             System.in.read();
301         }
302 
303         System.out.print( prompt );
304         System.out.print( ' ' );
305         MaskingThread thread = new MaskingThread();
306         thread.start();
307         try
308         {
309 
310             return in.readLine();
311         }
312         finally
313         {
314             // stop masking
315             thread.stopMasking();
316 
317         }
318     }
319 
320     // based on ideas from http://java.sun.com/developer/technicalArticles/Security/pwordmask/
321     class MaskingThread
322         extends Thread
323     {
324         private volatile boolean stop;
325 
326         /**
327          * Begin masking until asked to stop.
328          */
329         public void run()
330         {
331             // this needs to be high priority to make sure the characters don't
332             // really get to the screen.
333 
334             int priority = Thread.currentThread().getPriority();
335             Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
336 
337             try
338             {
339                 stop = false;
340                 while ( !stop )
341                 {
342                     // print a backspace + * to overwrite anything they type
343                     System.out.print( "\010*" );
344                     try
345                     {
346                         // attempt masking at this rate
347                         Thread.sleep( 1 );
348                     }
349                     catch ( InterruptedException iex )
350                     {
351                         Thread.currentThread().interrupt();
352                         return;
353                     }
354                 }
355             }
356             finally
357             {
358                 // restore the original priority
359                 Thread.currentThread().setPriority( priority );
360             }
361         }
362 
363         /**
364          * Instruct the thread to stop masking.
365          */
366         public void stopMasking()
367         {
368             this.stop = true;
369         }
370     }
371 }