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}