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