001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (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 General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.chunk; 019 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Map; 026 027import javax.crypto.spec.SecretKeySpec; 028 029import org.syncany.crypto.CipherSession; 030import org.syncany.crypto.CipherSpec; 031import org.syncany.crypto.CipherSpecs; 032import org.syncany.crypto.MultiCipherInputStream; 033import org.syncany.crypto.MultiCipherOutputStream; 034import org.syncany.crypto.SaltedSecretKey; 035import org.syncany.util.StringUtil; 036 037/** 038 * The CipherTransformer can be used to encrypt/decrypt files (typically 039 * {@link MultiChunk}s) using the {@link MultiCipherOutputStream} and 040 * {@link MultiCipherInputStream}. 041 * 042 * A CipherTransformer requires a list of {@link CipherSpec}s and the master 043 * key. It can be instantiated using a property list (from a config file) or 044 * by passing the dependencies to the constructor. 045 * 046 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 047 */ 048public class CipherTransformer extends Transformer { 049 public static final String TYPE = "cipher"; 050 public static final String PROPERTY_CIPHER_SPECS = "cipherspecs"; 051 public static final String PROPERTY_MASTER_KEY = "masterkey"; 052 public static final String PROPERTY_MASTER_KEY_SALT = "mastersalt"; 053 054 private List<CipherSpec> cipherSpecs; 055 private CipherSession cipherSession; 056 057 public CipherTransformer() { 058 this.cipherSpecs = new ArrayList<CipherSpec>(); 059 this.cipherSession = null; 060 } 061 062 public CipherTransformer(List<CipherSpec> cipherSpecs, SaltedSecretKey masterKey) { 063 this.cipherSpecs = cipherSpecs; 064 this.cipherSession = new CipherSession(masterKey); 065 } 066 067 /** 068 * Initializes the cipher transformer using a settings map. Required settings 069 * are: {@link #PROPERTY_CIPHER_SPECS}, {@link #PROPERTY_MASTER_KEY} and 070 * {@link #PROPERTY_MASTER_KEY_SALT}. 071 */ 072 @Override 073 public void init(Map<String, String> settings) throws Exception { 074 String masterKeyStr = settings.get(PROPERTY_MASTER_KEY); 075 String masterKeySaltStr = settings.get(PROPERTY_MASTER_KEY); 076 String cipherSpecsListStr = settings.get(PROPERTY_CIPHER_SPECS); 077 078 if (masterKeyStr == null || masterKeySaltStr == null || cipherSpecsListStr == null) { 079 throw new Exception("Settings '"+PROPERTY_CIPHER_SPECS+"', '"+PROPERTY_MASTER_KEY+"' and '"+PROPERTY_MASTER_KEY_SALT+"' must both be filled."); 080 } 081 082 initCipherSpecs(cipherSpecsListStr); 083 initCipherSession(masterKeyStr, masterKeySaltStr); 084 } 085 086 private void initCipherSpecs(String cipherSpecListStr) throws Exception { 087 String[] cipherSpecIdStrs = cipherSpecListStr.split(","); 088 089 for (String cipherSpecIdStr : cipherSpecIdStrs) { 090 int cipherSpecId = Integer.parseInt(cipherSpecIdStr); 091 CipherSpec cipherSpec = CipherSpecs.getCipherSpec(cipherSpecId); 092 093 if (cipherSpec == null) { 094 throw new Exception("Cannot find cipher suite with ID '"+cipherSpecId+"'"); 095 } 096 097 cipherSpecs.add(cipherSpec); 098 } 099 } 100 101 private void initCipherSession(String masterKeyStr, String masterKeySaltStr) { 102 byte[] masterKeySalt = StringUtil.fromHex(masterKeySaltStr); 103 byte[] masterKeyBytes = StringUtil.fromHex(masterKeyStr); 104 105 SaltedSecretKey masterKey = new SaltedSecretKey(new SecretKeySpec(masterKeyBytes, "RAW"), masterKeySalt); 106 cipherSession = new CipherSession(masterKey); 107 } 108 109 @Override 110 public OutputStream createOutputStream(OutputStream out) throws IOException { 111 if (cipherSession == null) { 112 throw new RuntimeException("Cipher session is not initialized. Call init() before!"); 113 } 114 115 return new MultiCipherOutputStream(out, cipherSpecs, cipherSession); 116 } 117 118 @Override 119 public InputStream createInputStream(InputStream in) throws IOException { 120 if (cipherSession == null) { 121 throw new RuntimeException("Cipher session is not initialized. Call init() before!"); 122 } 123 124 return new MultiCipherInputStream(in, cipherSession); 125 } 126 127 @Override 128 public String toString() { 129 return (nextTransformer == null) ? "Cipher" : "Cipher-"+nextTransformer; 130 } 131}