001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.store;
019    
020    import java.io.UnsupportedEncodingException;
021    import java.util.HashMap;
022    import java.util.Map;
023    
024    import javax.crypto.BadPaddingException;
025    import javax.crypto.Cipher;
026    import javax.crypto.IllegalBlockSizeException;
027    import javax.crypto.spec.IvParameterSpec;
028    import javax.crypto.spec.SecretKeySpec;
029    
030    import org.cumulus4j.store.crypto.AbstractCryptoManager;
031    import org.cumulus4j.store.crypto.AbstractCryptoSession;
032    import org.cumulus4j.store.crypto.Ciphertext;
033    import org.cumulus4j.store.crypto.CryptoContext;
034    import org.cumulus4j.store.crypto.CryptoSession;
035    import org.cumulus4j.store.crypto.Plaintext;
036    
037    /**
038     * Dummy crypto-manager for debugging and testing.
039     *
040     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
041     */
042    public class DummyCryptoManager extends AbstractCryptoManager
043    {
044            public static final String KEY_STORE_ID_COMPATIBILITY_TEST = "COMPATIBILITYTEST"; // no special characters!
045    
046            @Override
047            protected CryptoSession createCryptoSession() {
048                    return new DummySession();
049            }
050    
051            private static final class DummySession
052            extends AbstractCryptoSession
053            {
054    //              // key length: 128 bits
055    //              private static final byte[] dummyKey = { 'D', 'e', 'r', ' ', 'F', 'e', 'r', 'd', ' ', 'h', 'a', 't', ' ', 'v', 'i', 'e' };
056                    // initialization vector length: 128 bits
057                    private static final IvParameterSpec iv = new IvParameterSpec(new byte[] {'b', 'l', 'a', 't', 'r', 'u', 'l', 'l', 'a', 'l', 'a', 't', 'r', 'a', 'r', 'a'});
058    
059                    private static final String ALGORITHM = "AES";
060                    private static final String ALGORITHM_WITH_PARAMS = ALGORITHM + "/CBC/PKCS5Padding";
061    
062    //              private Cipher encrypter;
063    //              private Cipher decrypter;
064    //              {
065    //                      try {
066    //                              SecretKeySpec key = new SecretKeySpec(dummyKey, ALGORITHM);
067    //                              encrypter = Cipher.getInstance(ALGORITHM_WITH_PARAMS);
068    //                              encrypter.init(Cipher.ENCRYPT_MODE, key, iv);
069    //                              decrypter = Cipher.getInstance(ALGORITHM_WITH_PARAMS);
070    //                              decrypter.init(Cipher.DECRYPT_MODE, key, iv);
071    //                      } catch (Exception ex) {
072    //                              throw new RuntimeException(ex);
073    //                      }
074    //              }
075    
076                    private Map<Integer, Map<String, Cipher>> mode2KeyStoreID2Cipher = new HashMap<Integer, Map<String,Cipher>>();
077    
078                    protected Cipher getEncrypter() {
079                            return getCipher(Cipher.ENCRYPT_MODE);
080                    }
081    
082                    protected Cipher getDecrypter() {
083                            return getCipher(Cipher.DECRYPT_MODE);
084                    }
085    
086                    protected Cipher getCipher(int mode) {
087                            String keyStoreID = getKeyStoreID();
088    
089                            synchronized (mode2KeyStoreID2Cipher) {
090                                    Map<String, Cipher> keyStoreID2Cipher = mode2KeyStoreID2Cipher.get(mode);
091                                    if (keyStoreID2Cipher == null) {
092                                            keyStoreID2Cipher = new HashMap<String, Cipher>();
093                                            mode2KeyStoreID2Cipher.put(mode, keyStoreID2Cipher);
094                                    }
095    
096                                    Cipher cipher = keyStoreID2Cipher.get(keyStoreID);
097                                    if (cipher == null) {
098                                            // key length: 128 bits
099                                            byte[] dummyKey = { 'D', 'e', 'r', ' ', 'F', 'e', 'r', 'd', ' ', 'h', 'a', 't', ' ', 'v', 'i', 'e' };
100    
101                                            if (!KEY_STORE_ID_COMPATIBILITY_TEST.equals(keyStoreID)) {
102                                                    try {
103                                                            byte[] keyStoreIDBytes = keyStoreID.getBytes("UTF-8");
104                                                            int keyIdx = -1;
105                                                            for (int i = 0; i < keyStoreIDBytes.length; ++i) {
106                                                                    if (++keyIdx >= dummyKey.length)
107                                                                            keyIdx = 0;
108    
109                                                                    dummyKey[keyIdx] ^= keyStoreIDBytes[i];
110                                                            }
111                                                    } catch (UnsupportedEncodingException e) {
112                                                            throw new RuntimeException(e);
113                                                    }
114                                            }
115    
116                                            try {
117                                                    SecretKeySpec key = new SecretKeySpec(dummyKey, ALGORITHM);
118                                                    cipher = Cipher.getInstance(ALGORITHM_WITH_PARAMS);
119                                                    cipher.init(mode, key, iv);
120                                            } catch (Exception ex) {
121                                                    throw new RuntimeException(ex);
122                                            }
123                                            keyStoreID2Cipher.put(keyStoreID, cipher);
124                                    }
125                                    return cipher;
126                            }
127                    }
128    
129                    @Override
130                    public Ciphertext encrypt(CryptoContext cryptoContext, Plaintext plaintext)
131                    {
132                            // First get the required resources (that are cleared in close()).
133                            Cipher c = getEncrypter();
134    
135                            // Then assert that we are not yet closed. This makes sure that we definitely can continue
136                            // even if close() is called right now simultaneously.
137                            assertNotClosed();
138    
139                            Ciphertext result = new Ciphertext();
140                            result.setKeyID(12345);
141    
142                            synchronized (c) {
143                                    try {
144                                            result.setData(
145                                                            c.doFinal(plaintext.getData())
146                                            );
147                                    } catch (IllegalBlockSizeException e) {
148                                            throw new RuntimeException(e);
149                                    } catch (BadPaddingException e) {
150                                            throw new RuntimeException(e);
151                                    }
152                            }
153    
154                            return result;
155                    }
156    
157                    @Override
158                    public Plaintext decrypt(CryptoContext cryptoContext, Ciphertext ciphertext)
159                    {
160                            if (ciphertext.getKeyID() != 12345)
161                                    throw new IllegalArgumentException("No key with this keyID: " + ciphertext.getKeyID());
162    
163                            // First get the required resources (that are cleared in close()).
164                            Cipher c = getDecrypter();
165    
166                            // Then assert that we are not yet closed. This makes sure that we definitely can continue
167                            // even if close() is called right now simultaneously.
168                            assertNotClosed();
169    
170                            Plaintext result = new Plaintext();
171    
172                            synchronized (c) {
173                                    try {
174                                            result.setData(
175                                                            c.doFinal(ciphertext.getData())
176                                            );
177                                    } catch (IllegalBlockSizeException e) {
178                                            throw new RuntimeException(e);
179                                    } catch (BadPaddingException e) {
180                                            throw new RuntimeException(e);
181                                    }
182                            }
183    
184                            return result;
185                    }
186    
187                    @Override
188                    public void close() {
189                            super.close();
190                            synchronized (mode2KeyStoreID2Cipher) {
191                                    mode2KeyStoreID2Cipher.clear();
192                            }
193                    }
194            }
195    
196    }