001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.openpgp.ant;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.IOException;
023    import org.apache.commons.openpgp.BouncyCastleKeyRing;
024    import org.apache.commons.openpgp.BouncyCastleOpenPgpSignatureVerifier;
025    import org.apache.commons.openpgp.KeyRing;
026    import org.apache.commons.openpgp.OpenPgpException;
027    import org.apache.commons.openpgp.OpenPgpSignatureVerifier;
028    import org.apache.commons.openpgp.SignatureStatus;
029    import org.apache.tools.ant.BuildException;
030    import org.apache.tools.ant.Task;
031    import org.apache.tools.ant.types.Mapper;
032    import org.apache.tools.ant.util.FileNameMapper;
033    import org.apache.tools.ant.util.FileUtils;
034    import org.apache.tools.ant.util.GlobPatternMapper;
035    import org.bouncycastle.openpgp.PGPException;
036    
037    /**
038     * Verify a signature using the Bouncy Castle OpenPGP provider.
039     *
040     * @author <a href="mailto:dennisl@apache.org">Dennis Lundberg</a>
041     */
042    public class OpenPgpVerifierTask extends Task {
043        private File secring;
044        private File pubring;
045        private String password;
046        private File artefact;
047        private boolean asciiarmor = true;
048        private Mapper mapperElement;
049        private String verifyproperty;
050    
051        /**
052         * Set the secret keyring.
053         * @param secring secret keyring file
054         */
055        public void setSecring(File secring) {
056            this.secring = secring;
057        }
058    
059        /**
060         * Set the public keyring.
061         * @param pubring public keyring file
062         */
063        public void setPubring(File pubring) {
064            this.pubring = pubring;
065        }
066    
067        /**
068         * Use ASCII armored signature files?
069         * @param asciiarmor ascii armored signatures?
070         */
071        public void setAsciiarmor(boolean asciiarmor) {
072            this.asciiarmor = asciiarmor;
073        }
074    
075        /**
076         * Set the value of the password.
077         * @param password value of the password
078         */
079        public void setPassword(String password) {
080            this.password = password;
081        }
082    
083        /**
084         * Set the artefact to be handled.
085         * @param artefact artefact to be handled
086         */
087        public void setArtefact(File artefact) {
088            this.artefact = artefact;
089        }
090    
091        /**
092         * Set the name of the property that contains the result of the verification.
093         * @param verifyproperty name of the property
094         */
095        public void setVerifyproperty(String verifyproperty) {
096            this.verifyproperty = verifyproperty;
097        }
098    
099        /**
100         * Define the mapper to map source to destination files.
101         * @return a mapper to be configured.
102         * @exception org.apache.tools.ant.BuildException if more than one mapper is defined.
103         */
104        public Mapper createMapper() throws BuildException {
105            if (mapperElement != null) {
106                throw new BuildException("Cannot define more than one mapper",
107                        getLocation());
108            }
109            mapperElement = new Mapper(getProject());
110            return mapperElement;
111        }
112    
113        public void execute() {
114            if (secring == null) {
115                throw new BuildException("secring attribute compulsory");
116            }
117            if (pubring == null) {
118                throw new BuildException("pubring attribute compulsory");
119            }
120            if (password == null) {
121                throw new BuildException("password attribute compulsory");
122            }
123            if (artefact == null) {
124                throw new BuildException("The 'artefact' attribute is compulsory.");
125            }
126            if (verifyproperty == null) {
127                throw new BuildException("The 'verifyproperty' attribute is compulsory.");
128            }
129            if (!secring.exists() || !secring.canRead()) {
130                throw new  BuildException("secret keyring file '" + secring.getAbsolutePath() + "' does not exist or is not readable");
131            }
132            if (!pubring.exists() || !pubring.canRead()) {
133                throw new  BuildException("public keyring file '" + pubring.getAbsolutePath() + "' does not exist or is not readable");
134            }
135            FileInputStream secStream;
136            FileInputStream pubStream;
137            KeyRing keyRing = null;
138            try {
139                secStream = new FileInputStream(secring);
140                pubStream = new FileInputStream(pubring);
141                keyRing = new BouncyCastleKeyRing(secStream,
142                        pubStream, password.toCharArray() );
143            } catch (IOException ioe) {
144                throw new BuildException(ioe);
145            } catch (PGPException pgpe) {
146                throw new BuildException(pgpe);
147            }
148            if (artefact != null) {
149                doHandle(keyRing, artefact);
150            }
151            FileUtils.close(secStream);
152            FileUtils.close(pubStream);
153        }
154    
155        private void doHandle(KeyRing keyRing, File oneartefact) {
156            doHandle(keyRing, oneartefact, oneartefact.getParentFile(), oneartefact.getName());
157        }
158    
159        private void doHandle(KeyRing keyRing, File oneartefact, File basedir, String relpath) {
160            FileInputStream artifactFis = null;
161            FileInputStream signatureFis = null;
162            File signature;
163            boolean isValid = false;
164    
165            try {
166                artifactFis = new FileInputStream(oneartefact);
167                FileNameMapper mapper = getMapper();
168                String [] mappedFiles = mapper.mapFileName(relpath);
169                if (mappedFiles == null || mappedFiles.length != 1) {
170                    throw new BuildException("mapper returned more or less than one output");
171                }
172                signature = new File(basedir, mappedFiles[0]);
173                signatureFis = new FileInputStream(signature);
174                OpenPgpSignatureVerifier verifier = new BouncyCastleOpenPgpSignatureVerifier();
175                SignatureStatus status = verifier.verifyDetachedSignature(artifactFis, signatureFis, keyRing);
176                isValid = status.isValid();
177            } catch (FileNotFoundException fnfe) {
178                throw new BuildException(fnfe);
179            } catch (IOException ioe) {
180                throw new BuildException(ioe);
181            } catch (OpenPgpException opgpe) {
182                throw new BuildException(opgpe);
183            }
184            finally {
185                getProject().setProperty(verifyproperty, Boolean.toString(isValid));
186            }
187            FileUtils.close(signatureFis);
188            FileUtils.close(artifactFis);
189        }
190    
191        /**
192         * Return the mapper to use based on nested elements or use a default mapping.
193         */
194        private FileNameMapper getMapper() {
195            FileNameMapper mapper = null;
196            if (mapperElement != null) {
197                mapper = mapperElement.getImplementation();
198            } else {
199                mapper = new GlobPatternMapper();
200                mapper.setFrom("*");
201                if (asciiarmor) {
202                    mapper.setTo("*.asc");
203                } else {
204                    mapper.setTo("*.sig");
205                }
206            }
207            return mapper;
208        }
209    }