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 javax.crypto.SecretKey; 022import javax.crypto.spec.SecretKeySpec; 023 024import com.nimbusds.jose.util.Base64URL; 025import com.nimbusds.jose.util.ByteUtils; 026import com.nimbusds.oauth2.sdk.id.Subject; 027import net.jcip.annotations.ThreadSafe; 028import org.apache.commons.lang3.tuple.ImmutablePair; 029import org.apache.commons.lang3.tuple.Pair; 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 Pair<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 ImmutablePair<>(new SectorID(parts[0]), new Subject(parts[1])); 154 } 155}