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}