Index: src/main/java/org/apache/wiki/api/exceptions/EncryptionException.java
===================================================================
--- src/main/java/org/apache/wiki/api/exceptions/EncryptionException.java (revision 0)
+++ src/main/java/org/apache/wiki/api/exceptions/EncryptionException.java (working copy)
@@ -0,0 +1,39 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.wiki.api.exceptions;
+
+/**
+ * A generic exception for anything with the crypto libraries.
+ *
+ * @since 2.10.2
+ */
+public class EncryptionException extends WikiException {
+
+ private static final long serialVersionUID = -490652869936404653L;
+
+ /**
+ * Constructs an exception.
+ *
+ * @param msg {@inheritDoc}
+ */
+ public EncryptionException( String msg ) {
+ super( msg );
+ }
+
+}
Index: src/main/java/org/apache/wiki/crypto/CryptoProvider.java
===================================================================
--- src/main/java/org/apache/wiki/crypto/CryptoProvider.java (revision 0)
+++ src/main/java/org/apache/wiki/crypto/CryptoProvider.java (working copy)
@@ -0,0 +1,34 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.wiki.crypto;
+
+import org.apache.wiki.WikiProvider;
+import org.apache.wiki.api.exceptions.EncryptionException;
+
+import javax.crypto.Cipher;
+
+/**
+ * Provides cryptographic encryption and decryption services
+ */
+public interface CryptoProvider extends WikiProvider {
+ public static final String DEFAULT_CRYPTO_FILENAME = "jspwiki-crypto.properties";
+
+ public byte[] encrypt(char[] key, byte[] content) throws EncryptionException;
+ public byte[] decrypt(char[] key, byte[] content) throws EncryptionException;
+}
Index: src/main/java/org/apache/wiki/crypto/DefaultCryptoProvider.java
===================================================================
--- src/main/java/org/apache/wiki/crypto/DefaultCryptoProvider.java (revision 0)
+++ src/main/java/org/apache/wiki/crypto/DefaultCryptoProvider.java (working copy)
@@ -0,0 +1,173 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.wiki.crypto;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.exceptions.EncryptionException;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.exceptions.ProviderException;
+import org.apache.wiki.util.TextUtil;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+/**
+ * Provides cryptographic encryption and decryption services.
+ *
+ * See: Oracle CryptoSpec.html
+ *
+ * The default implementation here uses a password-based encryption (PBE) cipher.
+ *
+ * The configuration expects crypto.file in jspwiki-custom.properties which is the absolute
+ * path to a file specifying the other cryptographic properties. Ideally this file should be well
+ * protected, and read only.
+ *
+ * Properties within the "crypto.file" include:
+ *
+ *
+ * | property |
+ *
description |
+ *
+ *
+ * | crypto.key |
+ * The password that will do the encryption and decryption of content. Required |
+ *
+ *
+ * | crypto.prefix |
+ * A value used to determine if the page content is correctly decrypted. Should be a random string of minimum 10 length |
+ *
+ *
+ * | crypto.suffix |
+ * A value appended to the end of the encrypted string. Should be a random string of minimum 10 length |
+ *
+ *
+ */
+public class EncryptedPageFilter extends BasicPageFilter {
+
+ private static final Logger log = Logger.getLogger(EncryptedPageFilter.class);
+
+ private static final int MIN_LENGTH = 10;
+ public static final String PROP_CRYPTO_PROVIDER = "crypto.provider";
+ public static final String PROP_CRYPTO_FILE = "crypto.file";
+ public static final String PROP_CRYPTO_KEY = "crypto.key";
+ public static final String PROP_CRYPTO_PREFIX = "crypto.prefix";
+ public static final String PROP_CRYPTO_SUFFIX = "crypto.suffix";
+
+ private CryptoProvider cryptoProvider;
+ private char[] key;
+ private String prefix;
+ private String suffix;
+
+ @Override
+ public void initialize(WikiEngine engine, Properties properties) throws FilterException {
+
+ String providerClassName = TextUtil.getStringProperty(properties, PROP_CRYPTO_PROVIDER, "org.apache.wiki.crypto.DefaultCryptoProvider");
+
+ try {
+ cryptoProvider = (CryptoProvider) ClassUtil.getMappedObject(providerClassName);
+ } catch (WikiException e) {
+ throw new FilterException("Could not create filter for "+PROP_CRYPTO_PROVIDER+"="+providerClassName+". "+e.getMessage());
+ }
+
+ Properties cryptoProperties = new Properties();
+ try {
+
+ String filename = TextUtil.getStringProperty(properties, PROP_CRYPTO_FILE, CryptoProvider.DEFAULT_CRYPTO_FILENAME);
+ File f = new File(filename);
+
+ if (!f.exists()) {
+ log.warn("The file specified by " + PROP_CRYPTO_FILE + "=" + f.getAbsolutePath() + " does not exist!");
+ } else {
+ cryptoProperties.load(new FileReader(f));
+ }
+ } catch (Exception e) {
+ throw new FilterException(e.getMessage());
+ }
+
+ try {
+ key = TextUtil.getRequiredProperty(cryptoProperties, PROP_CRYPTO_KEY).toCharArray();
+ if (key.length < MIN_LENGTH) {
+ throw new FilterException("The encryption key "+PROP_CRYPTO_KEY+" provided is to short. Min Length required is " + MIN_LENGTH);
+ }
+ prefix = TextUtil.getStringProperty(cryptoProperties, PROP_CRYPTO_PREFIX, "A@I@#!)KDAS)$:");
+ if (prefix.length() < MIN_LENGTH) {
+ throw new FilterException("The property value "+PROP_CRYPTO_PREFIX+"="+ prefix + " is to short. Min Length required is " + MIN_LENGTH);
+ }
+ suffix = TextUtil.getStringProperty(cryptoProperties, PROP_CRYPTO_SUFFIX, ")FEJ*$Y@LSDFKJ@V");
+ if (suffix.length() < MIN_LENGTH) {
+ throw new FilterException("The property value "+PROP_CRYPTO_SUFFIX+"="+ suffix + " is to short. Min Length required is " + MIN_LENGTH);
+ }
+ } catch (NoRequiredPropertyException e) {
+ throw new FilterException(e.getMessage());
+ }
+ }
+
+ @Override
+ public String preSave(WikiContext wikiContext, String content) throws FilterException {
+ try {
+ content = prefix + content;
+ content = new String(cryptoProvider.encrypt(key, content.getBytes()));
+ content += suffix;
+ } catch (Exception e) {
+ throw new FilterException("Error encrypting content. ERROR="+e+" "+e.getMessage());
+ }
+ return super.preSave(wikiContext, content);
+ }
+
+ @Override
+ public String preTranslate(WikiContext wikiContext, String content) throws FilterException {
+ String result = super.preTranslate(wikiContext, content);
+ try {
+ if (result.endsWith(suffix)) {
+ result = result.substring(0,result.length()-suffix.length());
+ String plain = new String(cryptoProvider.decrypt(key, result.getBytes()));
+ if (plain.startsWith(prefix)) {
+ result = plain.substring(prefix.length());
+ }
+ }
+
+ } catch (Exception e) {
+ throw new FilterException("Error decrypting content. ERROR="+e+" "+e.getMessage());
+ }
+ return result;
+ }
+
+}
Index: src/main/java/org/apache/wiki/providers/EncryptedFileSystemProvider.java
===================================================================
--- src/main/java/org/apache/wiki/providers/EncryptedFileSystemProvider.java (revision 0)
+++ src/main/java/org/apache/wiki/providers/EncryptedFileSystemProvider.java (working copy)
@@ -0,0 +1,155 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.wiki.providers;
+
+import org.apache.log4j.Logger;
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.WikiPage;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.exceptions.ProviderException;
+import org.apache.wiki.api.exceptions.WikiException;
+import org.apache.wiki.crypto.CryptoProvider;
+import org.apache.wiki.util.ClassUtil;
+import org.apache.wiki.util.TextUtil;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * This class uses a {@link org.apache.wiki.crypto.CryptoProvider} to encrypt and decrypt page content.
+ *
+ * The default crypto provider is {@link org.apache.wiki.crypto.DefaultCryptoProvider}.
+ *
+ * An alternative crypto provider can be set by setting
crypto.provider in jspwiki-custom.properties
+ *
+ * This functionality only encrypts the page content, and not the page properties files.
+ * The encryption happens on a {@link org.apache.wiki.providers.FileSystemProvider:putPageText()} and
+ * decryption on {@link org.apache.wiki.providers.FileSystemProvider:getPageText()}.
+ * This means one page is encrypted at a time.
+ *
+ * The configuration expects
crypto.file in jspwiki-custom.properties which is the absolute
+ * path to a file specifying the other cryptographic properties. Ideally this file should be well
+ * protected, and read only.
+ *
+ *
+ *
+ * | property |
+ * description |
+ *
+ *
+ * | crypto.key |
+ * The password that will do the encryption and decryption of content. Required |
+ *
+ *
+ * | crypto.prefix |
+ * A value used to determine if the page content is correctly decrypted. Should be a random string of minimum 10 length |
+ *
+ *
+ * | crypto.suffix |
+ * A value appended to the end of the encrypted string. Should be a random string of minimum 10 length |
+ *
+ *
+ */
+public class EncryptedFileSystemProvider extends FileSystemProvider {
+
+ private static final Logger log = Logger.getLogger(EncryptedFileSystemProvider.class);
+
+ private static final int MIN_LENGTH = 10;
+ public static final String PROP_CRYPTO_PROVIDER = "crypto.provider";
+ public static final String PROP_CRYPTO_FILE = "crypto.file";
+ public static final String PROP_CRYPTO_KEY = "crypto.key";
+ public static final String PROP_CRYPTO_PREFIX = "crypto.prefix";
+ public static final String PROP_CRYPTO_SUFFIX = "crypto.suffix";
+
+ private CryptoProvider cryptoProvider;
+ private char[] key;
+ private String prefix;
+ private String suffix;
+
+ @Override
+ public void initialize(WikiEngine engine, Properties properties) throws NoRequiredPropertyException, IOException {
+ super.initialize(engine, properties);
+
+ String providerClassName = TextUtil.getStringProperty(properties, PROP_CRYPTO_PROVIDER, "org.apache.wiki.crypto.DefaultCryptoProvider");
+ try {
+ cryptoProvider = (CryptoProvider) ClassUtil.getMappedObject(providerClassName);
+ } catch (WikiException e) {
+ throw new NoRequiredPropertyException("Could not create provider for "+providerClassName+". "+e.getMessage(),PROP_CRYPTO_PROVIDER);
+ }
+
+ Properties cryptoProperties = new Properties();
+ String filename = TextUtil.getStringProperty(properties,PROP_CRYPTO_FILE, CryptoProvider.DEFAULT_CRYPTO_FILENAME);
+ File f = new File(filename);
+ if (!f.exists()) {
+ log.warn("The file specified by " + PROP_CRYPTO_FILE + "=" + f.getAbsolutePath() + " does not exist!");
+ } else {
+ cryptoProperties.load(new FileReader(f));
+ }
+ key = TextUtil.getRequiredProperty(cryptoProperties, PROP_CRYPTO_KEY).toCharArray();
+ if (key.length