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.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.project.MavenProject;
31  import org.codehaus.plexus.util.Os;
32  import org.codehaus.plexus.util.cli.CommandLineException;
33  import org.codehaus.plexus.util.cli.CommandLineUtils;
34  import org.codehaus.plexus.util.cli.Commandline;
35  import org.codehaus.plexus.util.cli.DefaultConsumer;
36  
37  public class GpgSigner
38  {
39  
40      public static final String SIGNATURE_EXTENSION = ".asc";
41  
42      private boolean useAgent;
43  
44      private boolean isInteractive = true;
45  
46      private String keyname;
47  
48      private String passphrase;
49  
50      private File outputDir;
51  
52      private File buildDir;
53  
54      private File baseDir;
55  
56      private File homeDir;
57  
58      public void setInteractive( boolean b )
59      {
60          isInteractive = b;
61      }
62  
63      public void setUseAgent( boolean b )
64      {
65          useAgent = b;
66      }
67  
68      public void setKeyName( String s )
69      {
70          keyname = s;
71      }
72  
73      public void setPassPhrase( String s )
74      {
75          passphrase = s;
76      }
77  
78      public void setOutputDirectory( File out )
79      {
80          outputDir = out;
81      }
82  
83      public void setBuildDirectory( File out )
84      {
85          buildDir = out;
86      }
87  
88      public void setBaseDirectory( File out )
89      {
90          baseDir = out;
91      }
92  
93      public void setHomeDirectory( File homeDirectory )
94      {
95          homeDir = homeDirectory;
96      }
97  
98      public File generateSignatureForArtifact( File file )
99          throws MojoExecutionException
100     {
101         File signature = new File( file + SIGNATURE_EXTENSION );
102 
103         boolean isInBuildDir = false;
104         if ( buildDir != null )
105         {
106             File parent = signature.getParentFile();
107             if ( buildDir.equals( parent ) )
108             {
109                 isInBuildDir = true;
110             }
111         }
112         if ( !isInBuildDir && outputDir != null )
113         {
114             String fileDirectory = "";
115             File signatureDirectory = signature;
116 
117             while ( ( signatureDirectory = signatureDirectory.getParentFile() ) != null )
118             {
119                 if ( !signatureDirectory.equals( baseDir ) )
120                 {
121                     fileDirectory = signatureDirectory.getName() + File.separatorChar + fileDirectory;
122                 }
123                 else
124                 {
125                     break;
126                 }
127             }
128             signatureDirectory = new File( outputDir, fileDirectory );
129             if ( !signatureDirectory.exists() )
130             {
131                 signatureDirectory.mkdirs();
132             }
133             signature = new File( signatureDirectory, file.getName() + SIGNATURE_EXTENSION );
134         }
135 
136         if ( signature.exists() )
137         {
138             signature.delete();
139         }
140 
141         Commandline cmd = new Commandline();
142 
143         cmd.setExecutable( "gpg" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" ) );
144 
145         if ( homeDir != null )
146         {
147             cmd.createArg().setValue( "--homedir" );
148             cmd.createArg().setFile( homeDir );
149         }
150 
151         if ( useAgent )
152         {
153             cmd.createArg().setValue( "--use-agent" );
154         }
155         else
156         {
157             cmd.createArg().setValue( "--no-use-agent" );
158         }
159 
160         InputStream in = null;
161         if ( null != passphrase )
162         {
163             cmd.createArg().setValue( "--passphrase-fd" );
164 
165             cmd.createArg().setValue( "0" );
166 
167             // Prepare the input stream which will be used to pass the passphrase to the executable
168             in = new ByteArrayInputStream( passphrase.getBytes() );
169         }
170 
171         if ( null != keyname )
172         {
173             cmd.createArg().setValue( "--local-user" );
174 
175             cmd.createArg().setValue( keyname );
176         }
177 
178         cmd.createArg().setValue( "--armor" );
179 
180         cmd.createArg().setValue( "--detach-sign" );
181 
182         if ( !isInteractive )
183         {
184             cmd.createArg().setValue( "--no-tty" );
185         }
186 
187         cmd.createArg().setValue( "--output" );
188         cmd.createArg().setFile( signature );
189 
190         cmd.createArg().setFile( file );
191 
192         try
193         {
194             int exitCode = CommandLineUtils.executeCommandLine( cmd, in, new DefaultConsumer(), new DefaultConsumer() );
195 
196             if ( exitCode != 0 )
197             {
198                 throw new MojoExecutionException( "Exit code: " + exitCode );
199             }
200         }
201         catch ( CommandLineException e )
202         {
203             throw new MojoExecutionException( "Unable to execute gpg command", e );
204         }
205 
206         return signature;
207     }
208 
209     private MavenProject findReactorProject( MavenProject prj )
210     {
211         if ( prj.getParent() != null )
212         {
213             if ( prj.getParent().getBasedir() != null && prj.getParent().getBasedir().exists() )
214             {
215                 return findReactorProject( prj.getParent() );
216             }
217         }
218         return prj;
219     }
220 
221     public String getPassphrase( MavenProject project )
222         throws IOException
223     {
224         String pass = null;
225 
226         if ( project != null )
227         {
228             pass = project.getProperties().getProperty( "gpg.passphrase" );
229             if ( pass == null )
230             {
231                 MavenProject prj2 = findReactorProject( project );
232                 pass = prj2.getProperties().getProperty( "gpg.passphrase" );
233             }
234         }
235         if ( pass == null )
236         {
237             // TODO: with JDK 1.6, we could call System.console().readPassword("GPG Passphrase: ", null);
238 
239             BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) );
240             while ( System.in.available() != 0 )
241             {
242                 // there's some junk already on the input stream, consume it
243                 // so we can get the real passphrase
244                 System.in.read();
245             }
246 
247             System.out.print( "GPG Passphrase:  " );
248             MaskingThread thread = new MaskingThread();
249             thread.start();
250 
251             pass = in.readLine();
252 
253             // stop masking
254             thread.stopMasking();
255         }
256         if ( project != null )
257         {
258             findReactorProject( project ).getProperties().setProperty( "gpg.passphrase", pass );
259         }
260         return pass;
261     }
262 
263     // based on ideas from http://java.sun.com/developer/technicalArticles/Security/pwordmask/
264     class MaskingThread
265         extends Thread
266     {
267         private volatile boolean stop;
268 
269         /**
270          * Begin masking until asked to stop.
271          */
272         public void run()
273         {
274             // this needs to be high priority to make sure the characters don't
275             // really get to the screen.
276 
277             int priority = Thread.currentThread().getPriority();
278             Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
279 
280             try
281             {
282                 stop = false;
283                 while ( !stop )
284                 {
285                     // print a backspace + * to overwrite anything they type
286                     System.out.print( "\010*" );
287                     try
288                     {
289                         // attempt masking at this rate
290                         Thread.sleep( 1 );
291                     }
292                     catch ( InterruptedException iex )
293                     {
294                         Thread.currentThread().interrupt();
295                         return;
296                     }
297                 }
298             }
299             finally
300             {
301                 // restore the original priority
302                 Thread.currentThread().setPriority( priority );
303             }
304         }
305 
306         /**
307          * Instruct the thread to stop masking.
308          */
309         public void stopMasking()
310         {
311             this.stop = true;
312         }
313     }
314 
315 }