1 package org.bouncycastle.cms.bc;
2 
3 import java.io.IOException;
4 import java.io.OutputStream;
5 import java.security.SecureRandom;
6 
7 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
8 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
9 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
10 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
11 import org.bouncycastle.cms.CMSException;
12 import org.bouncycastle.crypto.CipherKeyGenerator;
13 import org.bouncycastle.crypto.modes.AEADBlockCipher;
14 import org.bouncycastle.crypto.params.KeyParameter;
15 import org.bouncycastle.crypto.util.CipherFactory;
16 import org.bouncycastle.operator.DefaultSecretKeySizeProvider;
17 import org.bouncycastle.operator.GenericKey;
18 import org.bouncycastle.operator.MacCaptureStream;
19 import org.bouncycastle.operator.OutputAEADEncryptor;
20 import org.bouncycastle.operator.OutputEncryptor;
21 import org.bouncycastle.operator.SecretKeySizeProvider;
22 
23 public class BcCMSContentEncryptorBuilder
24 {
25     private static final SecretKeySizeProvider KEY_SIZE_PROVIDER = DefaultSecretKeySizeProvider.INSTANCE;
26 
27     private final ASN1ObjectIdentifier encryptionOID;
28     private final int keySize;
29 
30     private EnvelopedDataHelper helper = new EnvelopedDataHelper();
31     private SecureRandom random;
32 
BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)33     public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
34     {
35         this(encryptionOID, KEY_SIZE_PROVIDER.getKeySize(encryptionOID));
36     }
37 
BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)38     public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
39     {
40         this.encryptionOID = encryptionOID;
41         int fixedSize = KEY_SIZE_PROVIDER.getKeySize(encryptionOID);
42 
43         if (encryptionOID.equals(PKCSObjectIdentifiers.des_EDE3_CBC))
44         {
45             if (keySize != 168 && keySize != fixedSize)
46             {
47                 throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder.");
48             }
49             this.keySize = 168;
50         }
51         else if (encryptionOID.equals(OIWObjectIdentifiers.desCBC))
52         {
53             if (keySize != 56 && keySize != fixedSize)
54             {
55                 throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder.");
56             }
57             this.keySize = 56;
58         }
59         else
60         {
61             if (fixedSize > 0 && fixedSize != keySize)
62             {
63                 throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder.");
64             }
65             this.keySize = keySize;
66         }
67     }
68 
setSecureRandom(SecureRandom random)69     public BcCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
70     {
71         this.random = random;
72 
73         return this;
74     }
75 
build()76     public OutputEncryptor build()
77         throws CMSException
78     {
79         if (helper.isAuthEnveloped(encryptionOID))
80         {
81             return new CMSAuthOutputEncryptor(encryptionOID, keySize, random);
82         }
83         return new CMSOutputEncryptor(encryptionOID, keySize, random);
84     }
85 
86     private class CMSOutputEncryptor
87         implements OutputEncryptor
88     {
89         private KeyParameter encKey;
90         private AlgorithmIdentifier algorithmIdentifier;
91         protected Object cipher;
92 
CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)93         CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
94             throws CMSException
95         {
96             if (random == null)
97             {
98                 random = new SecureRandom();
99             }
100 
101             CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, keySize, random);
102 
103             encKey = new KeyParameter(keyGen.generateKey());
104 
105             algorithmIdentifier = helper.generateEncryptionAlgID(encryptionOID, encKey, random);
106 
107             cipher = EnvelopedDataHelper.createContentCipher(true, encKey, algorithmIdentifier);
108         }
109 
getAlgorithmIdentifier()110         public AlgorithmIdentifier getAlgorithmIdentifier()
111         {
112             return algorithmIdentifier;
113         }
114 
getOutputStream(OutputStream dOut)115         public OutputStream getOutputStream(OutputStream dOut)
116         {
117             return CipherFactory.createOutputStream(dOut, cipher);
118         }
119 
getKey()120         public GenericKey getKey()
121         {
122             return new GenericKey(algorithmIdentifier, encKey.getKey());
123         }
124     }
125 
126     private class CMSAuthOutputEncryptor
127         extends CMSOutputEncryptor
128         implements OutputAEADEncryptor
129     {
130         private AEADBlockCipher aeadCipher;
131         private MacCaptureStream macOut;
132 
CMSAuthOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)133         CMSAuthOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
134             throws CMSException
135         {
136             super(encryptionOID, keySize, random);
137 
138             aeadCipher = getCipher();
139         }
140 
getCipher()141         private AEADBlockCipher getCipher()
142         {
143             if (!(cipher instanceof AEADBlockCipher))
144             {
145                 throw new IllegalArgumentException("Unable to create Authenticated Output Encryptor without Authenticaed Data cipher!");
146             }
147             return (AEADBlockCipher)cipher;
148         }
149 
getOutputStream(OutputStream dOut)150         public OutputStream getOutputStream(OutputStream dOut)
151         {
152             macOut = new MacCaptureStream(dOut, aeadCipher.getMac().length);
153             return CipherFactory.createOutputStream(macOut, cipher);
154         }
155 
getAADStream()156         public OutputStream getAADStream()
157         {
158             return new AADStream(aeadCipher);
159         }
160 
getMAC()161         public byte[] getMAC()
162         {
163             return macOut.getMac();
164         }
165     }
166 
167     private static class AADStream
168         extends OutputStream
169     {
170         private AEADBlockCipher cipher;
171 
AADStream(AEADBlockCipher cipher)172         public AADStream(AEADBlockCipher cipher)
173         {
174             this.cipher = cipher;
175         }
176 
write(byte[] buf, int off, int len)177         public void write(byte[] buf, int off, int len)
178             throws IOException
179         {
180             cipher.processAADBytes(buf, off, len);
181         }
182 
write(int b)183         public void write(int b)
184             throws IOException
185         {
186             cipher.processAADByte((byte)b);
187         }
188     }
189 }
190