package org.bouncycastle.openpgp;

import java.io.EOFException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchProviderException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.InputStreamPacket;
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;

/**
 * A password based encryption object.
 */
public class PGPPBEEncryptedData
    extends PGPEncryptedData
{
    SymmetricKeyEncSessionPacket    keyData;
    
    PGPPBEEncryptedData(
        SymmetricKeyEncSessionPacket    keyData,
        InputStreamPacket               encData)
    {
        super(encData);
        
        this.keyData = keyData;
    }
    
    /**
     * Return the raw input stream for the data stream.
     * 
     * @return InputStream
     */
    public InputStream getInputStream()
    {
        return encData.getInputStream();
    }
    
    /**
     * Return the decrypted input stream, using the passed in passPhrase.
     * 
     * @param passPhrase
     * @param provider
     * @return InputStream
     * @throws PGPException
     * @throws NoSuchProviderException
     */
    public InputStream getDataStream(
        char[]                passPhrase,
        String                provider)
        throws PGPException, NoSuchProviderException
    {        
        Cipher c;
        
        try
        {
            if (encData instanceof SymmetricEncIntegrityPacket)
            {
                c =
                    Cipher.getInstance(
                        PGPUtil.getSymmetricCipherName(keyData.getEncAlgorithm()) + "/CFB/NoPadding",
                        provider);
            }
            else
            {
                c =
                    Cipher.getInstance(
                        PGPUtil.getSymmetricCipherName(keyData.getEncAlgorithm()) + "/OpenPGPCFB/NoPadding",
                        provider);
            }
        }
        catch (NoSuchProviderException e)
        {
           throw e;
        }
        catch (PGPException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new PGPException("exception creating cipher", e);
        }
        
        if (c != null)
        {
            try
            {
                SecretKey    key = PGPUtil.makeKeyFromPassPhrase(keyData.getEncAlgorithm(), keyData.getS2K(), passPhrase, provider);
                
                byte[]       iv = new byte[c.getBlockSize()];
                
                c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));

                encStream = new BCPGInputStream(new CipherInputStream(encData.getInputStream(), c));
                
                if (encData instanceof SymmetricEncIntegrityPacket)
                {
                    truncStream = new TruncatedStream(encStream);
                    encStream = new DigestInputStream(truncStream, MessageDigest.getInstance(PGPUtil.getDigestName(HashAlgorithmTags.SHA1), provider));
                }
                
                for (int i = 0; i != iv.length; i++)
                {
                    int    ch = encStream.read();
                    
                    if (ch < 0)
                    {
                        throw new EOFException("unexpected end of stream.");
                    }
                    
                    iv[i] = (byte)ch;
                }
                
                int    v1 = encStream.read();
                int    v2 = encStream.read();
                
                if (v1 < 0 || v2 < 0)
                {
                    throw new EOFException("unexpected end of stream.");
                }
                
                //
                // some versions of PGP appear to produce 0 for the extra
                // bytes rather than repeating the two previous bytes
                //
                if (iv[iv.length - 2] != (byte)v1 && v1 != 0)
                {
                    throw new PGPDataValidationException("data check failed.");
                }
                
                if (iv[iv.length - 1] != (byte)v2 && v2 != 0)
                {
                    throw new PGPDataValidationException("data check failed.");
                }
                
                return encStream;
            }
            catch (PGPException e)
            {
                throw e;
            }
            catch (Exception e)
            { 
                throw new PGPException("Exception creating cipher", e);
            }
        }
        else
        {
            return encData.getInputStream();
        }
    }
}

@@ -3945,32 +3945,34 @@
               %7D%0A
+%0A%0A
                 
@@ -3975,171 +3975,222 @@
     
-%0A                //%0A                // some versions of PGP appear to produce 0 for the extra%0A                // bytes rather than repeati
+// Note: the oracle attack on %22quick check%22 bytes is not deemed%0A                // a security risk for PBE (see PGPPublicKeyEncryptedData)%0A%0A                boolean repeatCheckPassed = iv%5Biv.le
 ng
- 
 th
-e two previous bytes
+ - 2%5D == (byte) v1
 %0A   
@@ -4206,19 +4206,16 @@
     
-//%0A
         
     
@@ -4206,36 +4206,27 @@
             
-        if (
+&& 
 iv%5Biv.length
@@ -4232,20 +4232,20 @@
 h - 
-2%5D !
+1%5D =
 = (byte)
 v1 &
@@ -4244,216 +4244,289 @@
 yte)
-v1 && v1 != 0)%0A                %7B%0A                    throw new PGPDataValidationException(%22data check failed.%22);%0A                %7D%0A                %0A                if (iv%5Biv.length - 1%5D != (byte)v2 && v2 != 0
+ v2;%0A%0A                // Note: some versions of PGP appear to produce 0 for the extra%0A                // bytes rather than repeating the two previous bytes%0A                boolean zeroesCheckPassed = v1 == 0 && v2 == 0;%0A%0A                if (!repeatCheckPassed && !zeroesCheckPassed
 )%0A  
@@ -4639,32 +4639,16 @@
       %7D%0A
-                
 %0A       

Fix the logic for PBE "quick check"