3333import org .owasp .esapi .Logger ;
3434import org .owasp .esapi .errors .EncodingException ;
3535import org .owasp .esapi .errors .EncryptionException ;
36+ import org .owasp .esapi .errors .EncryptionRuntimeException ;
37+ import org .owasp .esapi .errors .ConfigurationException ;
3638import org .owasp .esapi .errors .ValidationException ;
3739
3840///// IMPORTANT NOTE: Never print / log attribute *values* as they
3941///// may be sensitive. Also, do not log the CryptoToken
40- ///// itself as it generally is used as an authentication token.
41-
42- // OPEN ISSUE: Assertions vs. IllegalArgumentException must be resolved. I prefer
43- // assertions for preconditions, which is more in line with Design-by-Contract
44- // and Eiffel. There are a few places where assertions are not appropriate
45- // because if they are not disabled (they are not by default), they could cause
46- // incorrect behavior (e.g., setting the expiration time to something in the
47- // past, etc.)
42+ ///// itself, because even it is encrypted, the token itself
43+ ///// is often used as an authentication token.
4844
4945/**
5046 * Compute a cryptographically secure, encrypted token containing
7369 * The attribute value may contain any value. However, values containing
7470 * either '=' or ';' will be quoted using '\'. Likewise, values containing '\'
7571 * will also be quoted using '\'. Hence if original name/value pair were
76- * name=ab=xy\;
77- * this would be represented as
78- * name=ab\=xy\\\;
72+ * <b>name=ab=xy\;</b> * this would be represented as <b>name=ab\=xy\\\;</b>.
7973 * To ensure things are "safe" (from a security perspective), attribute
8074 * <i>names</i> must conform the the Java regular expression
8175 * <pre>
8276 * [A-Za-z0-9_\.-]+
8377 * </pre>
8478 * The attribute <i>value</i> on the other hand, may be any valid string. (That
85- * is, the value is not checked, so beware!)
79+ * is, the value is not checked, so beware! When rendering these values, output
80+ * encoding may be required to prevent XSS. Use ESAPI's {@code Encoder} for that.
8681 * <p>
8782 * This entire semicolon-separated string is then encrypted via one of the
8883 * {@code Encryptor.encrypt()} methods and then base64-encoded, serialized
9489 * must be unique. There are some restrictions on the attribute names.
9590 * (See the {@link #setAttribute(String, String)} method for details.)
9691 *
92+ * <b>WARNING:</b> You should never print / log attribute <b>values</b> as
93+ * they may be sensitive. Also, do not log the {@code CryptoToken}
94+ * itself, because even though it is encrypted, the token itself is
95+ * often used as an authentication token.
96+ *
97979898 * @since 2.0
9999 */
@@ -141,7 +141,9 @@ public CryptoToken() {
141141 * @param skey The specified {@code SecretKey} to use to encrypt the token.
142142 */
143143 public CryptoToken (SecretKey skey ) {
144- assert skey != null : "SecretKey may not be null." ;
144+ if ( skey == null ) {
145+ throw new IllegalArgumentException ("SecretKey may not be null." );
146+ }
145147 secretKey = skey ;
146148 long now = System .currentTimeMillis ();
147149 expirationTime = now + DEFAULT_EXP_TIME ;
@@ -170,8 +172,12 @@ public CryptoToken(String token) throws EncryptionException {
170172 throw new EncryptionException ("Decryption of token failed. Token improperly encoded or encrypted with different key." ,
171173 "Can't decrypt token because not correctly encoded or encrypted with different key." , e );
172174 }
173- assert username != null : "Programming error: Decrypted token found username null." ;
174- assert expirationTime > 0 : "Programming error: Decrypted token found expirationTime <= 0." ;
175+ if ( username == null ) {
176+ throw new IllegalArgumentException ("Programming error or malformed token: Decrypted token found username null." );
177+ }
178+ if ( expirationTime <= 0 ) {
179+ throw new IllegalArgumentException ("Programming error or malformed token: Decrypted token found expirationTime <= 0." );
180+ }
175181 }
176182
177183 /**
@@ -188,17 +194,27 @@ public CryptoToken(String token) throws EncryptionException {
188194 // token is a previously encrypted token (i.e., CryptoToken.getToken())
189195 // with different SecretKey other than the one in ESAPI.properties
190196 public CryptoToken (SecretKey skey , String token ) throws EncryptionException {
191- assert skey != null : "SecretKey may not be null." ;
192- assert token != null : "Token may not be null" ;
197+ if ( skey == null ) {
198+ throw new IllegalArgumentException ("SecretKey may not be null." );
199+ }
200+ if ( token == null ) {
201+ throw new IllegalArgumentException ("Token may not be null" );
202+ }
193203 secretKey = skey ;
194204 try {
195205 decryptToken (secretKey , token );
196206 } catch (EncodingException e ) {
197207 throw new EncryptionException ("Decryption of token failed. Token improperly encoded." ,
198208 "Can't decrypt token because not correctly encoded." , e );
199209 }
200- assert username != null : "Programming error: Decrypted token found username null." ;
201- assert expirationTime > 0 : "Programming error: Decrypted token found expirationTime <= 0." ;
210+ if ( username == null ) {
211+ String exm = "Programming error???: Decrypted token found username null." ;
212+ throw new EncryptionException (exm , exm );
213+ }
214+ if ( expirationTime <= 0 ) {
215+ String exm = "Programming error???: Decrypted token found expirationTime <= 0." ;
216+ throw new EncryptionException (exm , exm );
217+ }
202218 }
203219
204220 /**
@@ -224,7 +240,9 @@ public String getUserAccountName() {
224240 * expression.)
225241 */
226242 public void setUserAccountName (String userAccountName ) throws ValidationException {
227- assert userAccountName != null : "User account name may not be null." ;
243+ if ( userAccountName == null ) {
244+ throw new IllegalArgumentException ("User account name may not be null." );
245+ }
228246
229247 // Converting to lower case first allows a simpler regex.
230248 String userAcct = userAccountName .toLowerCase ();
@@ -251,7 +269,7 @@ public boolean isExpired() {
251269 /**
252270 * Set expiration time to expire in 'interval' seconds (NOT milliseconds).
253271 * @param intervalSecs Number of seconds in the future from current date/time
254- * to set expiration. Must be positive.
272+ * to set expiration. Must be positive.
255273 */
256274 public void setExpiration (int intervalSecs ) throws IllegalArgumentException
257275 {
@@ -296,6 +314,9 @@ public void setExpiration(Date expirationDate) throws IllegalArgumentException
296314 * @return The current expiration time.
297315 */
298316 public long getExpiration () {
317+ // Assertion here is safe as it is just a sanity check and this check is
318+ // make elsewhere. Plus this is a getter, not a setting, so if it's
319+ // messed up, it happened somewhere else.
299320 assert expirationTime > 0L : "Programming error: Expiration time <= 0" ;
300321 return expirationTime ;
301322 }
@@ -318,16 +339,10 @@ public Date getExpirationDate() {
318339 */
319340 public void setAttribute (String name , String value ) throws ValidationException {
320341 if ( name == null || name .length () == 0 ) {
321- // CHECKME: Should this be an IllegalArgumentException instead? I
322- // would prefer an assertion here and state this as a precondition
323- // in the Javadoc.
324342 throw new ValidationException ("Null or empty attribute NAME encountered" ,
325343 "Attribute NAMES may not be null or empty string." );
326344 }
327345 if ( value == null ) {
328- // CHECKME: Should this be an IllegalArgumentException instead? I
329- // would prefer an assertion here and state this as a precondition
330- // in the Javadoc.
331346 throw new ValidationException ("Null attribute VALUE encountered for attr name " + name ,
332347 "Attribute VALUE may not be null; attr name: " + name );
333348 }
@@ -359,8 +374,9 @@ public void setAttribute(String name, String value) throws ValidationException {
359374 * @see #setAttribute(String, String)
360375 */
361376 public void addAttributes (final Map <String , String > attrs ) throws ValidationException {
362- // CHECKME: Assertion vs. IllegalArgumentException
363- assert attrs != null : "Attribute map may not be null." ;
377+ if ( attrs == null ) {
378+ throw new IllegalArgumentException ("Attribute map may not be null." );
379+ }
364380 Set < Entry <String ,String > > keyValueSet = attrs .entrySet ();
365381 Iterator <Entry <String , String >> it = keyValueSet .iterator ();
366382 while ( it .hasNext () ) {
@@ -560,7 +576,10 @@ private String getQuotedAttributes() {
560576
561577 // Quote any special characters in value.
562578 private static String quoteAttributeValue (String value ) {
563- assert value != null : "Program error: Value should not be null." ; // Empty is OK.
579+ if ( value == null ) {
580+ String exm = "Programming error???: Value should not be null." ; // Empty string is OK.
581+ throw new EncryptionRuntimeException (exm , exm );
582+ }
564583 StringBuilder sb = new StringBuilder ();
565584 char [] charArray = value .toCharArray ();
566585 for ( int i = 0 ; i < charArray .length ; i ++ ) {
@@ -608,20 +627,34 @@ private void decryptToken(SecretKey skey, String b64token) throws EncryptionExce
608627 try {
609628 token = ESAPI .encoder ().decodeFromBase64 (b64token );
610629 } catch (IOException e ) {
611- // CHECKME: Not clear if we should log the actual token itself. It's encrypted,
612- // but could be arbitrarily long, especially since it is not valid
613- // encoding. OTOH, it may help debugging as sometimes it may be a simple
614- // case like someone failing to apply some other type of encoding
615- // consistently (e.g., URL encoding), in which case logging this should
616- // make this pretty obvious once a few of these are logged.
630+ // Ideally, we'd like to be able to include the actual (munged) token itself.
631+ // It's encrypted, but could be arbitrarily long, especially since it is not valid
632+ // encoding. In theory, it may help debugging as sometimes it may be a simple
633+ // case like someone failing to apply some other type of encoding
634+ // consistently (e.g., URL encoding), in which case logging this should
635+ // make this pretty obvious once a few of these are logged.
636+ //
637+ // OTOH, since tokens may be used as authentication tokens (see
638+ // "WARNING" in the class Javadoc) some insider could intentionally botch
639+ // a token (e.g., just remove the base64 padding characters) just to
640+ // get an otherwise valid token logged somewhere they can access it.
641+ // Therefore, I have decided NOT to include in in the logMessage
642+ // part of the exception.
643+ //
644+ // Note that prior to ESAPI 2.2.0.0, the token _was_ logged, but has
645+ // now been corrected.
646+ //
617647 throw new EncodingException ("Invalid base64 encoding." ,
618- "Invalid base64 encoding. Encrypted token was: " + b64token );
648+ "Invalid base64 encoding for token [REDACTED]" );
619649 }
620650 CipherText ct = CipherText .fromPortableSerializedBytes (token );
621651 Encryptor encryptor = ESAPI .encryptor ();
622652 PlainText pt = encryptor .decrypt (skey , ct );
623653 String str = pt .toString ();
624- assert str .endsWith (DELIM ) : "Programming error: Expecting decrypted token to end with delim char, " + DELIM_CHAR ;
654+ if ( ! str .endsWith (DELIM ) ) {
655+ String exm = "Programming error???: Expecting decrypted token to end with delim char, " + DELIM_CHAR ;
656+ throw new EncryptionException (exm , exm );
657+ }
625658 char [] charArray = str .toCharArray ();
626659 int prevPos = -1 ; // Position of previous unquoted delimiter.
627660 int fieldNo = 0 ;
@@ -653,9 +686,15 @@ private void decryptToken(SecretKey skey, String b64token) throws EncryptionExce
653686 }
654687
655688 Object [] objArray = fields .toArray ();
656- assert fieldNo == objArray .length : "Program error: Mismatch of delimited field count." ;
689+ if ( fieldNo != objArray .length ) {
690+ String exm = "Programming error???: Mismatch of delimited field count." ;
691+ throw new EncryptionException (exm , exm );
692+ }
657693 logger .debug (Logger .EVENT_UNSPECIFIED , "Found " + objArray .length + " fields." );
658- assert objArray .length >= 2 : "Missing mandatory fields from decrypted token (username &/or expiration time)." ;
694+ if ( objArray .length < 2 ) {
695+ String exm = "Missing mandatory fields from decrypted token (username &/or expiration time)." ;
696+ throw new EncryptionException (exm , exm );
697+ }
659698 username = ((String )(objArray [0 ])).toLowerCase ();
660699 String expTime = (String )objArray [1 ];
661700 expirationTime = Long .parseLong (expTime );
@@ -689,12 +728,18 @@ private void decryptToken(SecretKey skey, String b64token) throws EncryptionExce
689728 }
690729
691730 private SecretKey getDefaultSecretKey (String encryptAlgorithm ) {
692- assert encryptAlgorithm != null : "Encryption algorithm cannot be null" ;
731+ if ( encryptAlgorithm == null ) {
732+ throw new IllegalArgumentException ("Encryption algorithm cannot be null" );
733+ }
693734 byte [] skey = ESAPI .securityConfiguration ().getMasterKey ();
694- assert skey != null : "Can't obtain master key, Encryptor.MasterKey" ;
695- assert skey .length >= 7 :
735+ if ( skey == null ) {
736+ throw new IllegalArgumentException ("Can't obtain master key, Encryptor.MasterKey" );
737+ }
738+ if ( skey .length < 7 ) {
739+ throw new ConfigurationException (
696740 "Encryptor.MasterKey must be at least 7 bytes. " +
697- "Length is: " + skey .length + " bytes." ;
741+ "Length is: " + skey .length + " bytes." );
742+ }
698743 // Set up secretKeySpec for use for symmetric encryption and decryption,
699744 // and set up the public/private keys for asymmetric encryption /
700745 // decryption.
0 commit comments