1111import com .perimeterx .utils .logger .LogReason ;
1212
1313import javax .crypto .Cipher ;
14+ import javax .crypto .NoSuchPaddingException ;
1415import javax .crypto .SecretKey ;
1516import javax .crypto .spec .IvParameterSpec ;
1617import javax .crypto .spec .SecretKeySpec ;
1718import java .io .IOException ;
1819import java .nio .charset .StandardCharsets ;
20+ import java .security .NoSuchAlgorithmException ;
1921import java .util .Arrays ;
2022
23+ import static com .perimeterx .utils .PXCommonUtils .cookieKeysToCheck ;
24+
2125/**
2226 * Created by nitzangoldfeder on 13/04/2017.
2327 */
@@ -38,7 +42,6 @@ public abstract class AbstractPXCookie implements PXCookie {
3842 protected PXConfiguration pxConfiguration ;
3943 protected String pxCookie ;
4044 protected JsonNode decodedCookie ;
41- protected String cookieKey ;
4245 protected String cookieOrig ;
4346
4447 public AbstractPXCookie (PXConfiguration pxConfiguration , CookieData cookieData , PXContext context ) {
@@ -49,7 +52,6 @@ public AbstractPXCookie(PXConfiguration pxConfiguration, CookieData cookieData,
4952 this .pxConfiguration = pxConfiguration ;
5053 this .userAgent = cookieData .isMobileToken () ? "" : cookieData .getUserAgent ();
5154 this .ip = cookieData .getIp ();
52- this .cookieKey = pxConfiguration .getCookieKey ();
5355 this .cookieVersion = cookieData .getCookieVersion ();
5456 }
5557
@@ -81,7 +83,7 @@ public boolean deserialize() throws PXCookieDecryptionException {
8183
8284 JsonNode decodedCookie ;
8385 if (this .pxConfiguration .isEncryptionEnabled ()) {
84- decodedCookie = this .decrypt ();
86+ decodedCookie = this .decrypt (context );
8587 } else {
8688 decodedCookie = this .decode ();
8789 }
@@ -94,7 +96,7 @@ public boolean deserialize() throws PXCookieDecryptionException {
9496 return true ;
9597 }
9698
97- private JsonNode decrypt () throws PXCookieDecryptionException {
99+ private JsonNode decrypt (PXContext context ) throws PXCookieDecryptionException {
98100 final String [] parts = this .pxCookie .split (":" );
99101 if (parts .length != 3 ) {
100102 throw new PXCookieDecryptionException ("Part length invalid" );
@@ -115,21 +117,30 @@ private JsonNode decrypt() throws PXCookieDecryptionException {
115117 final Cipher cipher ; // aes-256-cbc decryptData no salt
116118 try {
117119 cipher = Cipher .getInstance ("AES/CBC/PKCS5Padding" );
118- final int dkLen = KEY_LEN + cipher .getBlockSize ();
119- PBKDF2Parameters p = new PBKDF2Parameters (HMAC_SHA_256 , "UTF-8" , salt , iterations );
120- byte [] dk = new PBKDF2Engine (p ).deriveKey (this .cookieKey , dkLen );
121- byte [] key = Arrays .copyOf (dk , KEY_LEN );
122- byte [] iv = Arrays .copyOfRange (dk , KEY_LEN , dk .length );
123- SecretKey secretKey = new SecretKeySpec (key , "AES" );
124- IvParameterSpec parameterSpec = new IvParameterSpec (iv );
125- cipher .init (Cipher .DECRYPT_MODE , secretKey , parameterSpec );
126- final byte [] data = cipher .doFinal (encrypted , 0 , encrypted .length );
127-
128- String decryptedString = new String (data , StandardCharsets .UTF_8 );
129- return mapper .readTree (decryptedString );
130- } catch (Exception e ) {
131- throw new PXCookieDecryptionException ("Cookie decryption failed in reason => " .concat (e .getMessage ()));
120+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e ) {
121+ throw new PXCookieDecryptionException (e );
122+ }
123+ final int dkLen = KEY_LEN + cipher .getBlockSize ();
124+ PBKDF2Parameters p = new PBKDF2Parameters (HMAC_SHA_256 , "UTF-8" , salt , iterations );
125+
126+ for (String cookieKey : this .pxConfiguration .getCookieKeys ()) {
127+ try {
128+ byte [] dk = new PBKDF2Engine (p ).deriveKey (cookieKey , dkLen );
129+ byte [] key = Arrays .copyOf (dk , KEY_LEN );
130+ byte [] iv = Arrays .copyOfRange (dk , KEY_LEN , dk .length );
131+ SecretKey secretKey = new SecretKeySpec (key , "AES" );
132+ IvParameterSpec parameterSpec = new IvParameterSpec (iv );
133+ cipher .init (Cipher .DECRYPT_MODE , secretKey , parameterSpec );
134+ final byte [] data = cipher .doFinal (encrypted , 0 , encrypted .length );
135+
136+ String decryptedString = new String (data , StandardCharsets .UTF_8 );
137+ JsonNode result = mapper .readTree (decryptedString );
138+ context .setCookieKeyUsed (cookieKey );
139+ return result ;
140+ } catch (Exception ignored ) {
141+ }
132142 }
143+ throw new PXCookieDecryptionException ("Cookie decryption failed" );
133144 }
134145
135146 private JsonNode decode () throws PXCookieDecryptionException {
@@ -152,7 +163,8 @@ public boolean isExpired() {
152163 }
153164
154165 public boolean isHmacValid (String hmacStr , String cookieHmac ) {
155- boolean isValid = HMACUtils .isHMACValid (hmacStr , cookieHmac , this .cookieKey , logger );
166+ boolean isValid = cookieKeysToCheck (this .context , this .pxConfiguration ).stream ()
167+ .anyMatch (cookieKey -> HMACUtils .isHMACValid (hmacStr , cookieHmac , cookieKey , logger ));
156168 if (!isValid ) {
157169 context .logger .debug (LogReason .DEBUG_COOKIE_DECRYPTION_HMAC_FAILED , pxCookie , this .userAgent );
158170 }
0 commit comments