Skip to content

Commit 8a5a01c

Browse files
committed
Replace assertions w/ runtime checks & logging fix.
Numerous 'assert' statements were replaced with explicit runtime checks where warranted. Also, in the case were the base64 decoding of a CryptoToken fails, that value is no longer output to the ESAPI log via an exception message.
1 parent 39d79b6 commit 8a5a01c

File tree

1 file changed

+89
-44
lines changed

1 file changed

+89
-44
lines changed

src/main/java/org/owasp/esapi/crypto/CryptoToken.java

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,14 @@
3333
import org.owasp.esapi.Logger;
3434
import org.owasp.esapi.errors.EncodingException;
3535
import org.owasp.esapi.errors.EncryptionException;
36+
import org.owasp.esapi.errors.EncryptionRuntimeException;
37+
import org.owasp.esapi.errors.ConfigurationException;
3638
import 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
@@ -73,16 +69,15 @@
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
@@ -94,6 +89,11 @@
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+
*
9797
9898
* @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

Comments
 (0)