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.SecretKey;
024import javax.crypto.spec.SecretKeySpec;
025
026import com.nimbusds.jose.util.Base64URL;
027import com.nimbusds.jose.util.ByteUtils;
028import com.nimbusds.oauth2.sdk.id.Subject;
029import net.jcip.annotations.ThreadSafe;
030import org.cryptomator.siv.SivMode;
031
032
033/**
034 * SIV AES - based encoder / decoder of pairwise subject identifiers. Requires
035 * a 256, 384, or 512-bit secret key. Reversal is supported.
036 *
037 * <p>The plain text is formatted as follows ('|' as delimiter):
038 *
039 * <pre>
040 * sector_id|local_sub
041 * </pre>
042 *
043 * <p>Related specifications:
044 *
045 * <ul>
046 *     <li>Synthetic Initialization Vector (SIV) Authenticated Encryption Using
047 *         the Advanced Encryption Standard (AES) (RFC 5297).
048 *     <li>OpenID Connect Core 1.0, section 8.1.
049 * </ul>
050 */
051@ThreadSafe
052public class SIVAESBasedPairwiseSubjectCodec extends PairwiseSubjectCodec {
053        
054        
055        /**
056         * The AES SIV crypto engine.
057         */
058        private static final SivMode AES_SIV = new SivMode();
059        
060        
061        /**
062         * The AES CTR key (1st half).
063         */
064        private final byte[] aesCtrKey;
065        
066        
067        /**
068         * The MAC key (2nd half).
069         */
070        private final byte[] macKey;
071        
072        
073        /**
074         * Creates a new SIV AES - based codec for pairwise subject
075         * identifiers.
076         *
077         * @param secretKey A 256, 384, or 512-bit secret key. Must not be
078         *                  {@code null}.
079         */
080        public SIVAESBasedPairwiseSubjectCodec(final SecretKey secretKey) {
081                super(null);
082                if (secretKey == null) {
083                        throw new IllegalArgumentException("The SIV AES secret key must not be null");
084                }
085                
086                byte[] keyBytes = secretKey.getEncoded();
087                
088                switch (keyBytes.length) {
089                        case 32:
090                                aesCtrKey = ByteUtils.subArray(keyBytes, 0, 16);
091                                macKey = ByteUtils.subArray(keyBytes, 16, 16);
092                                break;
093                        case 48:
094                                aesCtrKey = ByteUtils.subArray(keyBytes, 0, 24);
095                                macKey = ByteUtils.subArray(keyBytes, 24, 24);
096                                break;
097                        case 64:
098                                aesCtrKey = ByteUtils.subArray(keyBytes, 0, 32);
099                                macKey = ByteUtils.subArray(keyBytes, 32, 32);
100                                break;
101                        default:
102                                throw new IllegalArgumentException("The SIV AES secret key length must be 256, 384 or 512 bits");
103                }
104        }
105        
106        
107        /**
108         * Returns the secret key.
109         *
110         * @return The key.
111         */
112        public SecretKey getSecretKey() {
113                
114                return new SecretKeySpec(ByteUtils.concat(aesCtrKey, macKey), "AES");
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 = AES_SIV.encrypt(aesCtrKey, macKey, plainText);
124                return new Subject(Base64URL.encode(cipherText).toString());
125        }
126        
127        
128        @Override
129        public Map.Entry<SectorID, Subject> decode(final Subject pairwiseSubject)
130                throws InvalidPairwiseSubjectException {
131                
132                byte[] cipherText = new Base64URL(pairwiseSubject.getValue()).decode();
133                
134                byte[] plainText;
135                try {
136                        plainText  = AES_SIV.decrypt(aesCtrKey, macKey, cipherText);
137                } catch (Exception e) {
138                        throw new InvalidPairwiseSubjectException("Decryption failed: " + e.getMessage(), e);
139                }
140                
141                String parts[] = new String(plainText, CHARSET).split("(?<!\\\\)\\|");
142                
143                // Unescape delimiter
144                for (int i=0; i<parts.length; i++) {
145                        parts[i] = parts[i].replace("\\|", "|");
146                }
147                
148                // Check format
149                if (parts.length != 2) {
150                        throw new InvalidPairwiseSubjectException("Invalid format: Unexpected number of tokens: " + parts.length);
151                }
152                
153                return new AbstractMap.SimpleImmutableEntry<>(new SectorID(parts[0]), new Subject(parts[1]));
154        }
155}