001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.pageStore; 018 019import java.io.Serializable; 020import java.security.SecureRandom; 021 022import javax.crypto.SecretKey; 023 024import org.apache.wicket.Application; 025import org.apache.wicket.MetaDataKey; 026import org.apache.wicket.WicketRuntimeException; 027import org.apache.wicket.page.IManageablePage; 028import org.apache.wicket.pageStore.crypt.DefaultCrypter; 029import org.apache.wicket.pageStore.crypt.ICrypter; 030import org.apache.wicket.util.lang.Args; 031 032/** 033 * A store that encrypts all pages before delegating and vice versa. 034 * <p> 035 * All pages passing through this store are restricted to be {@link SerializedPage}s. You can 036 * achieve this with 037 * <ul> 038 * <li>a {@link SerializingPageStore} delegating to this store and</li> 039 * <li>delegating to a store that does not deserialize its pages, e.g. a {@link DiskPageStore}.</li> 040 * </ul> 041 */ 042public class CryptingPageStore extends DelegatingPageStore 043{ 044 private static final MetaDataKey<SessionData> KEY = new MetaDataKey<>() 045 { 046 private static final long serialVersionUID = 1L; 047 }; 048 049 private final ICrypter crypter; 050 051 private final Application application; 052 053 /** 054 * @param delegate 055 * store to delegate to 056 * @param application 057 * the application 058 */ 059 public CryptingPageStore(IPageStore delegate, Application application) 060 { 061 super(delegate); 062 this.application = Args.notNull(application, "application"); 063 crypter = newCrypter(); 064 } 065 066 /** 067 * Pages are always serialized, so versioning is supported. 068 */ 069 @Override 070 public boolean supportsVersioning() 071 { 072 return true; 073 } 074 075 /** 076 * Supports asynchronous add if the delegate supports it. 077 */ 078 @Override 079 public boolean canBeAsynchronous(IPageContext context) 080 { 081 // session data must be added here *before* any asynchronous calls 082 // when session is no longer available 083 getSessionData(context); 084 085 return getDelegate().canBeAsynchronous(context); 086 } 087 088 private SessionData getSessionData(IPageContext context) 089 { 090 return context.getSessionData(KEY, () -> new SessionData(crypter 091 .generateKey(application.getSecuritySettings().getRandomSupplier().getRandom()))); 092 } 093 094 /** 095 * Create a new {@link ICrypter}. 096 */ 097 protected ICrypter newCrypter() 098 { 099 return application.getStoreSettings().getCrypter().get(); 100 } 101 102 @Override 103 public IManageablePage getPage(IPageContext context, int id) 104 { 105 IManageablePage page = getDelegate().getPage(context, id); 106 107 if (page != null) 108 { 109 if (page instanceof SerializedPage == false) 110 { 111 throw new WicketRuntimeException("CryptingPageStore expects serialized pages"); 112 } 113 SerializedPage serializedPage = (SerializedPage) page; 114 115 byte[] encrypted = serializedPage.getData(); 116 byte[] decrypted = getSessionData(context).decrypt(encrypted, crypter); 117 118 page = new SerializedPage(page.getPageId(), serializedPage.getPageType(), decrypted); 119 } 120 121 return page; 122 } 123 124 @Override 125 public void addPage(IPageContext context, IManageablePage page) 126 { 127 if (page instanceof SerializedPage == false) 128 { 129 throw new WicketRuntimeException("CryptingPageStore works with serialized pages only"); 130 } 131 132 SerializedPage serializedPage = (SerializedPage) page; 133 134 byte[] decrypted = serializedPage.getData(); 135 byte[] encrypted = getSessionData(context).encrypt(decrypted, crypter, 136 application.getSecuritySettings().getRandomSupplier().getRandom()); 137 138 page = new SerializedPage(page.getPageId(), serializedPage.getPageType(), encrypted); 139 140 getDelegate().addPage(context, page); 141 } 142 143 private static class SessionData implements Serializable 144 { 145 private static final long serialVersionUID = 1L; 146 147 private final SecretKey key; 148 149 public SessionData(SecretKey key) 150 { 151 Args.notNull(key, "key"); 152 153 this.key = key; 154 } 155 156 public byte[] encrypt(byte[] decrypted, ICrypter crypter, SecureRandom random) 157 { 158 return crypter.encrypt(decrypted, key, random); 159 } 160 161 public byte[] decrypt(byte[] encrypted, ICrypter crypter) 162 { 163 return crypter.decrypt(encrypted, key); 164 } 165 } 166}