001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.openid.connect.sdk.id;
019
020
021import java.util.AbstractMap;
022import java.util.Map;
023import javax.crypto.Cipher;
024import javax.crypto.SecretKey;
025import javax.crypto.spec.IvParameterSpec;
026
027import com.nimbusds.jose.util.Base64URL;
028import com.nimbusds.oauth2.sdk.id.Subject;
029import net.jcip.annotations.ThreadSafe;
030
031
032/**
033 * AES/CBC/PKCS5Padding based encoder / decoder of pairwise subject
034 * identifiers. The salt is used as the IV. Reversal is supported.
035 *
036 * <p><strong>Warning: </strong> This codec is deprecated. Use
037 * {@link SIVAESBasedPairwiseSubjectCodec} instead.
038 *
039 * <p>The plain text is formatted as follows ('|' as delimiter):
040 *
041 * <pre>
042 * sector_id|local_sub
043 * </pre>
044 *
045 * <p>Related specifications:
046 *
047 * <ul>
048 *     <li>OpenID Connect Core 1.0, section 8.1.
049 * </ul>
050 */
051@ThreadSafe
052@Deprecated
053public class AESBasedPairwiseSubjectCodec extends PairwiseSubjectCodec {
054
055
056        /**
057         * The AES key.
058         */
059        private final SecretKey aesKey;
060
061
062        /**
063         * Creates a new AES-based codec for pairwise subject identifiers.
064         *
065         * @param aesKey The AES key. Must not be {@code null}.
066         * @param salt   The salt. Must not be {@code null}.
067         */
068        public AESBasedPairwiseSubjectCodec(final SecretKey aesKey, final byte[] salt) {
069                super(salt);
070                if (salt == null) {
071                        throw new IllegalArgumentException("The salt must not be null");
072                }
073                if (aesKey == null) {
074                        throw new IllegalArgumentException("The AES key must not be null");
075                }
076                this.aesKey = aesKey;
077        }
078
079
080        /**
081         * Returns the AES key.
082         *
083         * @return The key.
084         */
085        public SecretKey getAESKey() {
086                return aesKey;
087        }
088
089
090        /**
091         * Creates a new AES/CBC/PKCS5Padding cipher using the configured
092         * JCE provider and salt.
093         *
094         * @param mode The cipher mode.
095         *
096         * @return The cipher.
097         */
098        private Cipher createCipher(final int mode) {
099
100                Cipher aesCipher;
101
102                try {
103                        if (getProvider() != null) {
104                                aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", getProvider());
105                        } else {
106                                aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
107                        }
108
109                        aesCipher.init(mode, aesKey, new IvParameterSpec(getSalt()));
110                } catch (Exception e) {
111                        throw new RuntimeException(e);
112                }
113
114                return aesCipher;
115        }
116
117
118        @Override
119        public Subject encode(final SectorID sectorID, final Subject localSub) {
120
121                // Join parameters, delimited by '\'
122                byte[] plainText = (sectorID.getValue().replace("|", "\\|") + '|' + localSub.getValue().replace("|", "\\|")).getBytes(CHARSET);
123                byte[] cipherText;
124                try {
125                        cipherText = createCipher(Cipher.ENCRYPT_MODE).doFinal(plainText);
126                } catch (Exception e) {
127                        throw new RuntimeException(e);
128                }
129
130                return new Subject(Base64URL.encode(cipherText).toString());
131        }
132
133
134        @Override
135        public Map.Entry<SectorID, Subject> decode(final Subject pairwiseSubject)
136                throws InvalidPairwiseSubjectException {
137
138                byte[] cipherText = new Base64URL(pairwiseSubject.getValue()).decode();
139
140                Cipher aesCipher = createCipher(Cipher.DECRYPT_MODE);
141
142                byte[] plainText;
143                try {
144                        plainText = aesCipher.doFinal(cipherText);
145                } catch (Exception e) {
146                        throw new InvalidPairwiseSubjectException("Decryption failed: " + e.getMessage(), e);
147                }
148
149                String parts[] = new String(plainText, CHARSET).split("(?<!\\\\)\\|");
150
151                // Unescape delimiter
152                for (int i=0; i<parts.length; i++) {
153                        parts[i] = parts[i].replace("\\|", "|");
154                }
155
156                // Check format
157                if (parts.length != 2) {
158                        throw new InvalidPairwiseSubjectException("Invalid format: Unexpected number of tokens: " + parts.length);
159                }
160
161                return new AbstractMap.SimpleImmutableEntry<>(new SectorID(parts[0]), new Subject(parts[1]));
162        }
163}