001    package org.apache.commons.openpgp;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.bouncycastle.bcpg.ArmoredOutputStream;
021    import org.bouncycastle.bcpg.BCPGOutputStream;
022    import org.bouncycastle.bcpg.HashAlgorithmTags;
023    import org.bouncycastle.jce.provider.BouncyCastleProvider;
024    import org.bouncycastle.openpgp.PGPException;
025    import org.bouncycastle.openpgp.PGPPrivateKey;
026    import org.bouncycastle.openpgp.PGPSecretKey;
027    import org.bouncycastle.openpgp.PGPSignature;
028    import org.bouncycastle.openpgp.PGPSignatureGenerator;
029    import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
030    import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
031    import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
032    
033    import java.io.ByteArrayOutputStream;
034    import java.io.IOException;
035    import java.io.OutputStream;
036    import java.security.Security;
037    import java.security.SignatureException;
038    
039    /**
040     * Bouncy Castle implementation of the OpenPGP signer.
041     *
042     * @author <a href="mailto:brett@apache.org">Brett Porter</a>
043     */
044    public class BouncyCastleOpenPgpStreamingSigner
045        implements OpenPgpStreamingSigner
046    {
047        private static final String PROVIDER = "BC";
048    
049        private PGPSignatureGenerator sGen;
050    
051        private final ByteArrayOutputStream signatureBytes;
052    
053        private BCPGOutputStream bOut;
054    
055        public BouncyCastleOpenPgpStreamingSigner( String keyId, KeyRing keyRing, boolean asciiArmor )
056            throws OpenPgpException
057        {
058            signatureBytes = new ByteArrayOutputStream();
059            init( asciiArmor, signatureBytes, keyRing, keyId );
060        }
061    
062        public BouncyCastleOpenPgpStreamingSigner( OutputStream signature, String keyId, KeyRing keyRing,
063                                                   boolean asciiArmor )
064            throws OpenPgpException
065        {
066            signatureBytes = null;
067            init( asciiArmor, signature, keyRing, keyId );
068        }
069    
070        private void init( boolean asciiArmor, OutputStream signature, KeyRing keyRing, String keyId )
071            throws OpenPgpException
072        {
073            // TODO: better location for this?
074            Security.addProvider( new BouncyCastleProvider() );
075    
076            OutputStream out;
077            if ( asciiArmor )
078            {
079                out = new ArmoredOutputStream( signature );
080            }
081            else
082            {
083                out = signature;
084            }
085            bOut = new BCPGOutputStream( out );
086    
087            try
088            {
089                PGPSecretKey pgpSec = keyRing.getSecretKey( keyId );
090                if ( pgpSec == null )
091                {
092                    throw new OpenPgpException( "The key with id '" + keyId + "' was not found in the secret keyring." );
093                }
094                PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(
095                        new JcaPGPDigestCalculatorProviderBuilder().setProvider(PROVIDER).build()).setProvider(PROVIDER).
096                        build(keyRing.getPassword()));
097                sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(),
098                        HashAlgorithmTags.SHA1).setProvider(PROVIDER).setDigestProvider(PROVIDER));
099                sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
100            }
101            catch ( PGPException e )
102            {
103                // TODO: more details
104                throw new OpenPgpException( "Error calculating detached signature", e );
105            }
106        }
107    
108        public void update( byte[] buf )
109            throws OpenPgpException
110        {
111            update( buf, 0, buf.length );
112        }
113    
114        public void update( byte[] buf, int offset, int length )
115            throws OpenPgpException
116        {
117            try
118            {
119                sGen.update( buf, offset, length );
120            }
121            catch ( SignatureException e )
122            {
123                // TODO: more details
124                throw new OpenPgpException( "Error calculating detached signature", e );
125            }
126        }
127    
128        public byte[] finish()
129            throws OpenPgpException, IOException
130        {
131            try
132            {
133                sGen.generate().encode( bOut );
134            }
135            catch ( PGPException e )
136            {
137                // TODO: more details
138                throw new OpenPgpException( "Error calculating detached signature", e );
139            }
140            catch ( SignatureException e )
141            {
142                // TODO: more details
143                throw new OpenPgpException( "Error calculating detached signature", e );
144            }
145            bOut.close();
146            return signatureBytes != null ? signatureBytes.toByteArray() : null;
147        }
148    }