-
Notifications
You must be signed in to change notification settings - Fork 1.9k
JS: Use shared CryptographicOperation concept
#12080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…getInput() predicate
…f that algorithm is the most specific match
…icOperation#getBlockMode() for compatibility with external code
erik-krogh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, with some minor comments.
But we need a loud change-note, and some more instructions on how to migrate to the new code (both in change-note and QLDoc).
| /** | ||
| * An application of a cryptographic algorithm. | ||
| */ | ||
| abstract class CryptographicOperation extends DataFlow::Node { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is problematic.
You are changing an abstract class to a non abstract class that instead uses the ::Range pattern.
This will at minimum require a loud change-note.
CryptographicOperation is on the "global" scope in JavaScript, so we can't leave a deprecated class behind.
Anyone overridding the old CryptographicOperation class will get deprecation warnings, so I think it's good enough with a change-note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a change note that hopefully explains the required changes to any external implementations of CryptographicOperation clearly enough.
javascript/ql/lib/semmle/javascript/frameworks/CryptoLibraries.qll
Outdated
Show resolved
Hide resolved
javascript/ql/lib/semmle/javascript/frameworks/CryptoLibraries.qll
Outdated
Show resolved
Hide resolved
javascript/ql/lib/semmle/javascript/frameworks/CryptoLibraries.qll
Outdated
Show resolved
Hide resolved
| DataFlow::Node getAnInput() { result = super.getAnInput() } | ||
|
|
||
| /** DEPRECATED. This predicate has been renamed to `getAnInput`. */ | ||
| deprecated final DataFlow::Node getInput() { result = super.getInput() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will cause compile-errors for any external users that have extended this.
(Also see my former comment about CryptographicOperation and change-note).
I think we'll have to use breaking in the change-note.
I think the QLDoc should loudly explain what to do, which is both extend getAnInput instead, and make sure you extend CryptographicOperation::Range instead of CryptographicOperation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the QLDoc to mention this (the predicate itself has moved to Concepts.qll to avoid introducing a deprecated predicate to Ruby/Py as well). It's a little bit clumsy as the deprecation message has to read properly for both users and implementers of CryptographicOperation#getInput().
| abstract DataFlow::Node getAnInput(); | ||
|
|
||
| /** DEPRECATED. This predicate has been renamed to `getAnInput`. */ | ||
| deprecated final DataFlow::Node getInput() { result = this.getAnInput() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CryptographicOperation::Range#getInput() has been removed - I don't think there's a need to include this as the ::Range class is new for JS.
RasmusWL
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good to me 👍 Thanks for moving our languages closer to each other 💪
I'm wondering whether the changes in b968b59 to matchesName is important for anything, or it just seemed like a good idea? -- We've previously seen performance problems with the Python analysis when doing too much string manipulation, so that is why I'm interested in this bit in particular (and would at least like to see a performance evaluation before giving a final approval for this)
| /** DEPRECATED. This predicate has been renamed to `getAnInput`. */ | ||
| deprecated final DataFlow::Node getInput() { result = super.getInput() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: We don't need to add this deprecated predicate to the shared file, it's possible to only have it part of JS like we've done for Ruby in the past (example).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 it seems worth avoiding adding a deprecated predicate for all languages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed this from Ruby/Py in c7aaad9. The explicit aliases feel a bit clumsy, but I'm not aware of a good way to avoid them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, yes those aliases are a bit clumsy. I wonder if the new shadowing mechanism in QL would allow us to do it more nicely 🤔
| * is a stream cipher rather than a block cipher. | ||
| */ | ||
| abstract BlockMode getBlockMode(); | ||
| BlockMode getBlockMode() { none() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in other comments, it's possible to do this change of making it non-abstract for JS only.
My default stance on this is that it's nicer if the predicates you're supposed to implement are abstract, since that means you won't forget to do it. However, there is also part of me that is annoyed at having to implement getBlockMode for hashing cryptographic operations...
So I guess now is a good time to think about whether we want to change this to have a default of none() for all languages or not. What are your own thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been uncertain on this as the need to implement this for hashing operations is irritating. I'd briefly considered creating separate EncryptionOperation and HashingOperation (and maybe also PasswordHashingOperation) classes and only including the getBlockMode() predicate in EncryptionOperation, but I think this is maybe overcomplicating things since getAlgorithm() already works to restrict the operation to a particular kind of operation.
On reflection I think this change to have a default none() is a mistake. It's too easy to just forget to implement this predicate entirely if it's not abstract. I also don't think there's a need to make this change for JS specifically, since this is already a breaking change which will require changes to external implementations of CryptographicOperation.
My plan here is to change this back to being abstract (for all languages) and to improve the documentation on this to make it clearer when this should be implemented with a non-none() result (i.e. only when this is an encryption operation using a block cipher).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me 👍
There were some specific cases that I was trying to cover here. In where the version of which would match against both I'll start some DCA runs for all languages after a few changes are finalized here. |
…being an abstract predicate
erik-krogh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JS 👍
|
@RasmusWL I finally got together 3 successful DCA runs - I didn't notice any performance differences, though admittedly the test suites don't have that many projects. |
RasmusWL
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍 Thanks for making the changes. Performance evaluation looked fine as well 👍
This mostly affects JS, but also changes some behaviour for Ruby and Python as well. This change should allow us to add the Python version of
weak-cryptographic-algorithmas a (new) JS query.The JS and Ruby/Python versions of
CryptographicOperationwere pretty similar, with 2 main differences:getInput()in JS vsgetAnInput()in Ruby/PythonBlockMode getBlockMode()predicateI added a deprecated
getInput()predicate to the shared concept for the first difference, and implementedgetBlockMode()for the existing JSCryptographicOperationimplementations. There were 10 implementations inCryptoLibraries.qll, but only 4 of these libraries seemed to support encryption using block ciphers.There are a couple of other changes that affect all languages.
BlockMode#matchesString(string)predicate for convenienceCryptographicAlgorithm#matchesName(string).asmCryptouses"AES_OFB"as an algorithm name to specify AES encryption using the OFB block mode - previouslymatchesStringwould have just read this as"AESOFB"and not matched against AESmatchesNamenow only holds when the algorithm is the "most specific" matching algorithm - in practice this means the matching algorithm with the longest name. This is specifically for things like"SHA3_224"which has a valid match as bothSHA3andSHA3_224, but the latter is a more precise match. This is relevant for e.g. https://pycryptodome.readthedocs.io/en/latest/src/hash/sha3_224.html