[HN Gopher] Psychic Signatures in Java
       ___________________________________________________________________
        
       Psychic Signatures in Java
        
       Author : 19870213
       Score  : 120 points
       Date   : 2022-04-19 21:09 UTC (1 days ago)
        
 (HTM) web link (neilmadden.blog)
 (TXT) w3m dump (neilmadden.blog)
        
       | pluc wrote:
       | Not a good few months for Java
        
       | tptacek wrote:
       | This is probably the cryptography bug of the year. It's easy to
       | exploit and bypasses signature verification on anything using
       | ECDSA in Java, including SAML and JWT (if you're using ECDSA in
       | either).
       | 
       | The bug is simple: like a lot of number-theoretic asymmetric
       | cryptography, the core of ECDSA is algebra on large numbers
       | modulo some prime. Algebra in this setting works for the most
       | part like the algebra you learned in 9th grade; in particular,
       | zero times any algebraic expression is zero. An ECDSA signature
       | is a pair of large numbers (r, s) (r is the x-coordinate of a
       | randomly selected curve point based on the infamous ECDSA nonce;
       | s is the signature proof that combines x, the hash of the
       | message, and the secret key). The bug is that Java 15+ ECDSA
       | accepts (0, 0).
       | 
       | For the same bug in a simpler setting, just consider finite field
       | Diffie Hellman, where we agree on a generator G and a prime P,
       | Alice's secret key is `a mod P` and her public key is `G^a mod
       | P`; I do the same with B. Our shared secret is `A^b mod P` or
       | `B^a mod P`. If Alice (or a MITM) sends 0 (or 0 mod P) in place
       | of A, then they know what the result is regardless of anything
       | else: it's zero. The same bug recurs in SRP (which is sort of a
       | flavor of DH) and protocols like it (but much worse, because
       | Alice is proving that she knows a key and has an incentive to
       | send zero).
       | 
       | The math in ECDSA is more convoluted but not much more; the
       | kernel of ECDSA signature verification is extracting the `r`
       | embedded into `s` and comparing it to the presented `r`; if `r`
       | and `s` are both zero, that comparison will always pass.
       | 
       | It is much easier to mess up asymmetric cryptography than it is
       | to mess up most conventional symmetric cryptography, which is a
       | reason to avoid asymmetric cryptography when you don't absolutely
       | need it. This is a devastating bug that probably affects a lot of
       | different stuff. Thoughts and prayers to the Java ecosystem!
        
         | DyslexicAtheist wrote:
         | > Thoughts and prayers to the Java ecosystem!
         | 
         | some very popular PKI systems (many CA's) are powered by Java
         | and BouncyCastle ...
        
           | nmadden wrote:
           | BouncyCastle has its own implementation of ECDSA, and it's
           | not vulnerable to this bug.
        
         | na85 wrote:
         | >infamous ECDSA nonce
         | 
         | Why "infamous"?
        
           | Dylan16807 wrote:
           | I'm not particularly knowledgeable here, but I know it's
           | extremely fragile, far beyond just needing to be unique. See
           | "LadderLeak: Breaking ECDSA With Less Than One Bit of Nonce
           | Leakage"
        
           | SAI_Peregrinus wrote:
           | It's more properly called 'k'. It's really a secret key, but
           | it has to be unique per-signature. If an attacker can ever
           | guess a single bit of the nonce with probability non-
           | negligibly >50%, they can find the private key of whoever
           | signed the message(s).
           | 
           | It makes ECDSA _very_ brittle, and quite prone to side-
           | channel attacks (since those can get attackers exactly such
           | information.
        
             | Mindless2112 wrote:
             | There's an easy fix for that though -- generate k
             | deterministically using the procedure in RFC6979 [1].
             | 
             | [1]
             | https://datatracker.ietf.org/doc/html/rfc6979#section-3.2
        
             | drexlspivey wrote:
             | That makes no sense, how can you get the private key from
             | knowing 1 bit of the nonce?
        
               | tptacek wrote:
               | See, cryptography engineering is sinking in!
               | 
               | Here you go:
               | 
               | https://toadstyle.org/cryptopals/62.txt
               | 
               | What's especially great about this is that it's very easy
               | to accidentally have a biased nonce; in most other areas
               | of cryptography, all you care about when generating
               | random parameters is that they be sufficiently (ie, "128
               | bit security worth") random. But with ECDSA, you need the
               | entire domain of the k value to be random.
        
               | drexlspivey wrote:
               | Ok but for this scheme you need a large amount of
               | signatures from the same biased RNG which makes sense. I
               | thought that the GP was suggesting that you can recover
               | the key from one signature with just a few bits.
        
               | na85 wrote:
               | I guess like so:
               | 
               | https://cryptopals.com/sets/8/challenges/62.txt
               | 
               | E: Thomas beat me to it
        
         | Zababa wrote:
         | Thank you for that, that was a great explanation.
        
         | loup-vaillant wrote:
         | Interestingly, EdDSA (generally known as Ed25519) does not need
         | as many checks as ECDSA, and assuming the public key is valid,
         | an all-zero signature will be rejected with the main checks.
         | All you need to do is verify the following equation:
         | 
         |  _R = SB - Hash(R || A || M) A_
         | 
         | Where _R_ and _S_ are the two halves of the signature, _A_ is
         | the public key, and _M_ is the message (and _B_ is the curve 's
         | base point). If the signature is zero, the equation reduces to
         | _Hash(R || A || M)A = 0_ , which is always false with a
         | legitimate public key.
         | 
         | And indeed, TweetNaCl does not explicitly check that the
         | signature is not zero. It doesn't need to.
         | 
         |  _However._
         | 
         | There are still ways to be clever and shoot ourselves in the
         | foot. In particular, there's the temptation to convert the
         | Edwards point to Montgomery, perform the scalar multiplication
         | there, then convert back (doubles the code's speed compared to
         | a naive ladder). Unfortunately, doing that introduces edge
         | cases that weren't there before, that cause the point we get
         | back to be invalid. So invalid in fact that adding it to
         | another point gives us zero half the time or so, causing the
         | verification to succeed even though it should have failed!
         | 
         |  _(Pro tip: don 't bother with that conversion, variable time
         | double scalarmult https://loup-vaillant.fr/tutorials/fast-
         | scalarmult is even faster.)_
         | 
         | A pretty subtle error, though with eerily similar consequences.
         | It _looked_ like a beginner-nuclear-boyscout error, but my only
         | negligence there was messing with maths I only partially
         | understood. (A pretty big no-no, but I have learned my lesson
         | since.)
         | 
         | Now if someone could contact the Whycheproof team and get them
         | to fix their front page so people know they have EdDSA test
         | vectors, that would be great.
         | https://github.com/google/wycheproof/pull/79 If I had known
         | about those, the whole debacle could have been avoided. Heck, I
         | bet my hat their ECDSA test vectors could have avoided the
         | present Java vulnerability. They need to be advertised better.
        
       | ptx wrote:
       | Apparently you have to get a new CPU to fix this Java
       | vulnerability, or alternatively a new PSU.
       | 
       | (That is to say: a _Critical Patch Update_ or a _Patch Set
       | Update_. Did they really have to overload these TLAs?)
        
       | lobstey wrote:
       | Not that a lot of companies are using the Java 15+. People
       | generally stick to 8 or 11.
        
         | needusername wrote:
         | I believe Oracle 11 is affected.
        
       | cesarb wrote:
       | And once again, you'd be saved if you stayed on an older release.
       | This is the third time this has happened recently in the Java
       | world: the Spring4Shell vulnerability only applies to Java 9 and
       | later (that vulnerability depends on the existence of a method
       | introduced by Java 9, since all older methods were properly
       | blacklisted by Spring), and the Log4Shell vulnerability only
       | applies to log4j 2.x (so if you stayed with log4j 1.x, and didn't
       | explicitly configure it to use a vulnerable appender, you were
       | safe). What's going on with Java?
        
         | KronisLV wrote:
         | > ...the Log4Shell vulnerability only applies to log4j 2.x (so
         | if you stayed with log4j 1.x, and didn't explicitly configure
         | it to use a vulnerable appender, you were safe)
         | 
         | Seems like someone likes to live dangerously: using libraries
         | that haven't been updated since 2012 is a pretty risky move,
         | especially given that if an RCE is discovered now, you'll find
         | yourself without too many options to address it, short of
         | migrating over to the new release (which will be worse than
         | having to patch a single dependency in a backwards compatible
         | manner): https://logging.apache.org/log4j/1.2/changes-
         | report.html
         | 
         | Admittedly, i wrote a blog post called "Never update anything"
         | a while back, even if in a slightly absurdist manner:
         | https://blog.kronis.dev/articles/never-update-anything and
         | personally think that frequent updates are a pain to deal with,
         | but personally i'd only advocate for using stable/infrequently
         | updated pieces of software if they're still supported in one
         | way or another.
         | 
         | You do bring up a nice point about the recent influx of
         | vulnerabilities and problems in the Java ecosystem, which i
         | believe is created by the fact that they're moving ahead at a
         | faster speed and are attempting to introduce new language
         | features to stay relevant and make the language more inviting
         | for more developers.
         | 
         | That said, with how many GitHub outages there have been in the
         | past year and how many other pieces of software/services have
         | broken in a variety of ways, i feel like chasing after a more
         | rapid pace of changes and breaking things in the process is an
         | industry wide problem.
        
           | yardstick wrote:
           | > using libraries that haven't been updated since 2012 is a
           | pretty risky move
           | 
           | I disagree. Some libraries are just rock solid, well tested
           | and long life.
           | 
           | In the case of log4j 1.x vs 2.x, has there been any real
           | motivator to upgrade? There are 2 well known documented
           | vulnerabilities in 1.x that only apply if you use extensions.
        
             | KronisLV wrote:
             | A sibling comment mentions the reload4j project, so clearly
             | someone thought that 1.x wasn't adequate to a degree of
             | creating a new project around maintaining a fork. Can't
             | speak of the project itself, merely the fact that its
             | existence supports the idea that EOL software is something
             | that people would prefer to avoid, even if they decide to
             | maintain a backwards compatible fork themselves, which is
             | great to see.
             | 
             | Here's a bit more information about some of the
             | vulnerabilities in 1.x, someone did a nice writeup about
             | it: https://www.petefreitag.com/item/926.cfm
             | 
             | I've also dealt with 1.x having some issues with data loss,
             | for example, https://logging.apache.org/log4j/1.2/apidocs/o
             | rg/apache/log4... which is unlikely to get fixed:
             | DailyRollingFileAppender has been observed to exhibit
             | synchronization issues and data loss.
             | 
             | (though at least in regards to that problem, there are
             | alternatives; though for the most part EOL software implies
             | that no further fixes will be available)
             | 
             | But at the end of the day none of it really matters: those
             | who don't want to upgrade won't do so, potential issues
             | down the road (or even current ones that they're not aware
             | of) be damned. Similarly, others might have unpatched
             | versions of 2.x running somewhere which somehow haven't
             | been targeted by automated attacks (yet) and might continue
             | to do so while there isn't proper motivation to upgrade, or
             | won't do so until it will be too late.
             | 
             | Personally, i dislike the idea of using abandoned software
             | for the most part, when i just want to get things done - i
             | don't have the time to dance around old documentation, dead
             | links, having to figure out workarounds for CVEs versus
             | just using the latest (stable) versions and letting someone
             | else worry about it all down the road. Why take on an
             | additional liability, when most modern tooling and
             | framework integrations (e.g. Spring Boot) will be built
             | around the new stuff anyways? Though thankfully in regards
             | to this particular case slf4j gives you more flexibility,
             | but in general i'd prefer to use supported versions of
             | software.
             | 
             | I say that as someone who actually migrated a bunch of old
             | monolithic Spring (not Boot) apps to something more modern
             | when the versions had been EOL for a few years and there
             | were over a hundred CVEs as indicated by automated
             | dependency/package scanning. It took months to do, because
             | previously nobody actually cared to constantly follow the
             | new releases and thus it was more akin to a rewrite rather
             | than an update - absolute pain, especially that JDK 8 to 11
             | migration was also tacked on, as was containerizing the app
             | due to environmental inconsistencies growing throughout the
             | years to the point where the app would roll over and die
             | and nobody had any idea why (ahh, the joys of working with
             | monoliths, where even logs, JMX and heap dumps don't help
             | you).
             | 
             | Of course, after untangling that mess, i'd like to suggest
             | that you should not only constantly update packages (think
             | every week, alongside releases; you should also release
             | often) but also keep the surface area of any individual
             | service small enough that they can be easily
             | replaced/rewritten. Anyways, i'm going off on a tangent
             | here about the greater implications of using EOL stuff long
             | term, but those are my opinions and i simultaneously do
             | admit that there are exceptions to that approach and
             | circumstances vary, of course.
        
           | cesarb wrote:
           | > especially given that if an RCE is discovered now, you'll
           | find yourself without too many options to address it, short
           | of migrating over to the new release
           | 
           | Luckily, there's now an alternative: reload4j
           | (https://reload4j.qos.ch/) is a maintained fork of log4j 1.x,
           | so if you were one of the many who stayed on the older log4j
           | 1.x (and there were enough of them that there was sufficient
           | demand for that fork to be created), you can just migrate to
           | that fork (which is AFAIK fully backward compatible).
           | 
           | (And if you do want to migrate away from log4j 1.x, you don't
           | need to migrate to log4j 2.x; you could also migrate to
           | something else like logback.)
        
         | ragnese wrote:
         | Was Spring4Shell Java's fault, or Spring's fault? Log4Shell was
         | obviously (mostly) log4j's fault.
         | 
         | This one, I gather, is actually Java's fault.
         | 
         | It sounds like three unrelated security bugs from totally
         | different teams of developers.
        
           | brazzy wrote:
           | I think they other two are considered "Javas's fault" because
           | the frameworks they occurred in are so pervasive in the Java
           | ecosystem that you might as well consider them part of the
           | standard library.
        
         | jatone wrote:
         | _gasp_ new code can introduce bugs... whatever will one do?!
        
         | taeric wrote:
         | You make this sound like it is unique to java. I remember
         | heartbleed had similar, in that the lts I was on did not have
         | the vulnerable library.
         | 
         | At some level, as long as releases add functionality, the basic
         | rules of systemantics will guarantee unintended interactions.
        
       | ccbccccbbcccbb wrote:
       | Q: Which type of cryptography is implied to be unsafe in the
       | following sentence?:
       | 
       | "Immediately ditch RSA in favor of EC, for it is too hard to
       | implement safely!"
        
         | tedunangst wrote:
         | What's java's RSA history look like?
        
       | RandomBK wrote:
       | Does anyone know why this was only given a CVSS score of 7.5?
       | Based on the description this sounds way worse, but Oracle only
       | gave it a CVSS Confidentiality Score of "None", which doesn't
       | sound right. Is there some mitigating factor that hasn't been
       | discussed?
       | 
       | In terms of OpenJDK 17 (latest LTS), the issue is patched in
       | 17.0.3, which was release ~12h ago. Note that official OpenJDK
       | docker images are still on 17.0.2 as of time of writing.
        
         | tptacek wrote:
         | CVSS is a completely meaningless Ouija board that says whatever
         | the person authoring the score wants it to say.
        
       | m00dy wrote:
       | I thought java was disappeared in the previous ice age.
        
         | 0des wrote:
         | I want to live in this world, but no.
        
       | [deleted]
        
       | bertman wrote:
       | The fix for OpenJDK (authored on Jan. 4th 22):
       | 
       | https://github.com/openjdk/jdk/blob/e2f8ce9c3ff4518e070960ba...
        
         | sdhfkjwefs wrote:
         | Why are there no tests?
        
         | drexlspivey wrote:
         | with commit message "Improve ECDSA signature support" :D
        
       | tialaramex wrote:
       | This is the sort of dumb mistake that ought to get caught by unit
       | testing. A junior, assigned the task of testing this feature,
       | ought to see that in the cryptographic signature design these
       | values are checked as not zero, try setting them to zero, and...
       | watch it burn to the ground.
       | 
       | Except that, of course, people don't actually do unit testing,
       | they're too busy.
       | 
       | Somebody is probably going to mention fuzz testing. But, if
       | you're "too busy" to even write the unit tests for the software
       | you're about to replace, you aren't going to fuzz test it are
       | you?
        
         | tptacek wrote:
         | The point of fuzz testing is not having to think of test cases
         | in the first place.
        
           | kasey_junk wrote:
           | This is true in principle but in practice most fuzz testing
           | frameworks demand a fair bit of setup. It's worth it!
           | 
           | But if you are in a time constrained environment where basic
           | unit tests are skipped fuzz testing will be as well.
        
           | loup-vaillant wrote:
           | You still need your tests to cover all possible errors (or at
           | least all _plausible_ errors). If you try random numbers and
           | your prime happens to be close to a power of two, evenly
           | distributed random numbers won 't end up outside the [0,n-1]
           | range you are supposed to validate. Even if your prime is far
           | enough from a power of two, you still won't hit zero by
           | chance (and you need to test zero, because you almost
           | certainly need two separate pieces of code to reject the =0
           | and >=n cases).
           | 
           | Another example is Poly1305. When you look at the test
           | vectors from RFC 8439, you notice that some are specially
           | crafted to trigger overflows that random tests wouldn't
           | stumble upon.
           | 
           | Thus, I would argue that proper testing requires some domain
           | knowledge. Naive fuzz testing is bloody effective but it's
           | not enough.
        
             | cliftonk wrote:
             | That's all true, but fuzz testing is very effective at
             | checking boundary conditions (near 0, near max/mins) and
             | would have caught this particular problem easily.
        
               | loup-vaillant wrote:
               | Do you mean fuzz testing does _not_ use even
               | distributions? There's a bias towards extrema, or at
               | least some guarantee to test zero and MAX? I guess that
               | would work.
               | 
               | Also, would you consider the following to be fuzz
               | testing? https://github.com/LoupVaillant/Monocypher/blob/
               | master/tests...
        
           | tialaramex wrote:
           | [Somebody had down-voted you when I saw this, but it wasn't
           | me]
           | 
           | These aren't alternatives, they're complementary. I
           | appreciate that fuzz testing makes sense over writing unit
           | tests for weird edge cases, but "these parameters can't be
           | zero" isn't an edge case, it's part of the basic design.
           | Here's an example of what X9.62 says:
           | 
           | > If r' is not an integer in the interval [1, n-1], then
           | reject the signature.
           | 
           | Let's write a unit test to check say, zero here. Can we also
           | use fuzz testing? Sure, why not. But lines like this ought to
           | _scream out_ for a unit test.
        
             | tptacek wrote:
             | Right, I'm just saying: there's a logic that says fuzz
             | tests are easier than specific test-cases: the people that
             | run the fuzz tests barely need to understand the code at
             | all, just the basic interface for verifying a signature.
        
           | solarengineer wrote:
           | If we write an automated test case for known acceptance
           | criteria, and then write necessary and sufficient code to get
           | those tests to pass, we would know what known acceptance
           | criteria are being fulfilled. When someone else adds to the
           | code and causes a test to fail, the test case and the
           | specific acceptance criteria would thus help the developer
           | understand intended behaviour (verify behaviour, review
           | implementation). Thus, the test suite would become a
           | catalogue of programmatically verifiable acceptance criteria.
           | 
           | Certainly, fuzz tests would help us test boundary conditions
           | and more, but they are not a catalogue of known acceptance
           | criteria.
        
           | anfilt wrote:
           | While fuzz testing is good and all, when it comes to
           | cryptography, the input spaces is so large that chances of
           | finding something are even worse than finding a needle in a
           | hay stack.
           | 
           | For instance here the keys are going to be around 256 bits in
           | a size, so if your fuzzer is just picking keys at random,
           | your basically never likely to pick zero at random.
           | 
           | With cryptographic primitives you really should be testing
           | all known invalid input parameters for the particular
           | algorithm. A a random fuzzer is not going to know that.
           | Additionally, you should be testing inputs that can cause
           | overflows and are handled correctly ect...
        
         | hsbauauvhabzb wrote:
         | The issue is the assumption juniors should be writing the unit
         | tests, sounds like you might be part of the problem.
        
           | tialaramex wrote:
           | I think I probably technically count as a junior in my
           | current role, which is very amusing and "I don't write enough
           | unit tests" was one of the things I wrote in the self-
           | assessed annual review.
           | 
           | So, sure.
        
       | [deleted]
        
       | LaputanMachine wrote:
       | >Just a basic cryptographic risk management principle that
       | cryptography people get mad at me for saying (because it's true)
       | is: don't use asymmetric cryptography unless you absolutely need
       | it.
       | 
       | Is there any truth to this? Doesn't basically all Internet
       | traffic rely on the security of (correctly implemented)
       | asymmetric cryptography?
        
         | fabian2k wrote:
         | I've seen this argument often on the topic of JWTs, which are
         | also mentioned in the tweets here. In many situations there are
         | simpler methods than JWTs that don't require any cryptography,
         | e.g. simply storing session ids server-side. With these simple
         | methods there isn't anything cryptographic that could break or
         | be misused.
         | 
         | The TLS encryption is of course assumed here, but that is
         | nothing most developers ever really touch in a way that could
         | break it. And arguably this part falls under the "you
         | absolutely need it" exception.
        
           | jaywalk wrote:
           | Server-side session storage isn't necessarily a replacement
           | for JWTs. It can be in many cases, but it's not one to one.
           | JWTs do have advantages.
        
             | fabian2k wrote:
             | That's why I wrote "in many cases". The problem is more
             | that for a while at least JWT were pretty much sold as the
             | new and shiny replacement for classic sessions, which
             | they're not. They absolutely have their uses, but they also
             | have additional attack surface.
        
           | [deleted]
        
           | slaymaker1907 wrote:
           | You can still use encryption with JWTs if you use a symmetric
           | key. I believe HS256 just uses a symmetric key HMAC with
           | SHA256. If you go beyond JWT, Kerberos only uses symmetric
           | cryptography while not being as centralized as other
           | solutions. Obviously, the domain controller is centralized,
           | but it allows for various services to use common
           | authentication without compromising the whole domain if any
           | one service is compromised (assuming correct configuration
           | which is admittedly difficult with Kerberos).
        
           | er4hn wrote:
           | The biggest problem with JWTs is not what cryptography you
           | use (though there was a long standing issue where "none" was
           | something that clients could enter as a client side
           | attack...) but rather revocation.
           | 
           | x509 certificates have several revocation mechanisms since
           | having something being marked as "do not use" before the end
           | of its lifetime is well understood. JWTs are not quite there.
        
             | codebje wrote:
             | JWT is just a container for authenticated data. it's
             | comparable to the ASN.1 encoding of an x509 certificate,
             | not to the entire x509 public key infrastructure.
             | 
             | You could compare x509 with revocation to something like
             | oauth with JWT access tokens, though.
             | 
             | In that case, x509 certificates are typically expensive to
             | renew and have lifetimes measured in years. Revocation
             | involves clients checking a revocation service. JWT access
             | tokens are cheap to renew and have lifetimes measured in
             | minutes. Revocation involves denying a refresh token when
             | the access token needs renewing. Clients can also choose to
             | renew access tokens much more frequently if a 'revocation
             | server' experience is desirable.
             | 
             | Given the spotty history of CRLDP reliability, I think
             | oauth+JWT are doing very well in comparison. I'm pretty
             | damn confident that when I revoke an application in Google
             | or similar it will lose access very quickly.
        
         | lazide wrote:
         | Initial connection negotiation and key exchange does, anything
         | after that no. It will use some kind of symmetric algo
         | (generally AES).
         | 
         | It's a bad idea (and no one should be doing it) to continue
         | using asymmetric crypto algorithms after that. If someone can
         | get away with a pre-shared (symmetric) key, sometimes/usually
         | even better, depending on the risk profiles.
        
         | formerly_proven wrote:
         | I wouldn't be particularly worried of someone decrypting a file
         | encrypted in the 80s using Triple DES anytime soon. I don't
         | think I'll live to see AES being broken.
         | 
         | I wouldn't bet on the TLS session you're using to have that
         | kind of half life.
        
         | smegsicle wrote:
         | if people were getting mad at him, he must have been pretty
         | obnoxious about it because i don't think there's much
         | controversy- Asymmetric encryption is pretty much just used for
         | things like sharing the Symmetric key that will be used for the
         | rest of the session
         | 
         | of course it would be more secure to have private physical key
         | exchange, but that's not a practical option, so we rely on RSA
         | or whatever
        
         | nicoburns wrote:
         | > Is there any truth to this?
         | 
         | Yes, symmetric cryptography is a lot more straightforward and
         | should be preferred where it is easy to use a shared secret.
         | 
         | > Doesn't basically all Internet traffic rely on the security
         | of (correctly implemented) asymmetric cryptography?
         | 
         | It does. This would come under the "unless you absolutely need
         | it" exception.
        
       | lobstey wrote:
       | I doubt how many companies are actually using java15+. Many still
       | sticks to 8 or 11
        
       ___________________________________________________________________
       (page generated 2022-04-20 23:00 UTC)