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