Stream: wg-secure-code

Topic: secure rng


Tony Arcieri (Nov 25 2018 at 01:40, on Zulip):

Is CSRNG in Rust too complicated? https://github.com/rust-random/rand/issues/648

Tony Arcieri (Nov 25 2018 at 01:40, on Zulip):

I have followed this project for years and not voiced an opinion

Tony Arcieri (Nov 25 2018 at 01:41, on Zulip):

but now I'm like... what I want is very simple, and what I am getting I no longer fully understand, and that's a bit frightening

Tony Arcieri (Nov 25 2018 at 01:42, on Zulip):

I say this all as I deal with a probably unrelated RNG debacle

Tony Arcieri (Nov 25 2018 at 01:42, on Zulip):

but I'm also asking myself how did aforementioned debacle even happen

Tony Arcieri (Nov 25 2018 at 01:42, on Zulip):

because ideally I would like to see it never ever happen again

Tony Arcieri (Nov 25 2018 at 01:43, on Zulip):

tl;dr: I'm not sure the rand API is what Rust cryptography needs

Tony Arcieri (Nov 25 2018 at 01:45, on Zulip):

conflating the needs of cryptographically secure random number generators and non cryptographically secure number generators, even behind marker traits, at least now seems like a bad idea to me

Tony Arcieri (Nov 25 2018 at 01:46, on Zulip):

it feels like an attempt to build abstractions where those abstractions aren't useful except to attackers

briansmith (Nov 25 2018 at 02:29, on Zulip):

@Tony Arcieri Is water wet?

Tony Arcieri (Nov 25 2018 at 02:29, on Zulip):

haha

Tony Arcieri (Nov 25 2018 at 02:30, on Zulip):

@briansmith should've used ring? :wink:

Tony Arcieri (Nov 25 2018 at 02:30, on Zulip):

/me having a bit of trouble with ring as a dependency but perhaps we can take that elsewhere

briansmith (Nov 25 2018 at 02:31, on Zulip):

@Tony Arcieri Sure, DM me.

Tony Arcieri (Nov 25 2018 at 07:25, on Zulip):

well I spent my night researching what is effectively a vulnerability (albeit in alpha quality software no one is relying on) and so far my conclusions are it is correctly generating random numbers on macOS, but not Linux

Tony Arcieri (Nov 25 2018 at 07:25, on Zulip):

I am continuing to investigate

Tony Arcieri (Nov 25 2018 at 07:25, on Zulip):

this is very unfortunate, though

Tony Arcieri (Nov 25 2018 at 07:25, on Zulip):

and I would like to find a way to prevent this class of bug from ever, ever happening again (at least without a panic)

brycx (Nov 25 2018 at 09:34, on Zulip):

Are you able to provide any information on how usage of the OsRng (I presume it's that) can differ on Linux and macOS? First thing that comes to mind is using fill instead of try_fill and not correctly handling errors, but that seems unreasonable since fill handles things like waiting for an OS generator implicitly.

Shnatsel (Nov 25 2018 at 12:30, on Zulip):

There are other warts, such as the "give me random bytes" syscall being equivalent to reading from /dev/urandom as opposed to /dev/random, and you need the latter if you want to seed a cryptographically secure RNG in user-space. This is exacerbated by the fact that if your program running in a sandbox with no access to /dev/random, there is no way to get seed-quality entropy

Tony Arcieri (Nov 25 2018 at 17:02, on Zulip):

I'm using fill_bytes

Tony Arcieri (Nov 25 2018 at 17:03, on Zulip):

the code in question is here: https://github.com/tendermint/signatory/blob/master/src/ed25519/seed.rs#L44

Tony Arcieri (Nov 25 2018 at 17:37, on Zulip):

more fun: debug builds on Linux seem ok. It's only release builds which are impacted.

Tony Arcieri (Nov 25 2018 at 18:06, on Zulip):

this is at least no longer looking like the rand crate's fault

brycx (Nov 25 2018 at 18:25, on Zulip):

@Shnatsel I actually just tried to test that, by having a binary executed in a firejail sandbox with /dev/urandom/ and /dev/random blacklisted. When calling another library's generation function (OsRng) there were no errors reported, but when I tried to open /dev/urandom/ with std::File it panicked on PermissionDenied. Seems a bit curious, but I'm sure there's something I've overlooked.

Alex Gaynor (Nov 25 2018 at 18:34, on Zulip):

getrandom syscall I assume

brycx (Nov 25 2018 at 18:39, on Zulip):

Yeah, the sandbox logs do say that getrandom() has been called

brycx (Nov 25 2018 at 18:44, on Zulip):

FWIW it's the same behavior on both debug and release.

Alex Gaynor (Nov 25 2018 at 18:45, on Zulip):

Failing that it could also be the AT_RANDOM aux value.

brycx (Nov 25 2018 at 18:50, on Zulip):

IIUC that only provides a pointer to 16 bytes of random data? I'm generating 32.

Tony Arcieri (Nov 25 2018 at 18:53, on Zulip):

my issue ended up being an errant debug_assert_eq! which resulted in important code being compiled out of release builds. oops.

Shnatsel (Nov 25 2018 at 18:53, on Zulip):

so code under debug_assert_eq! had side-effects?

brycx (Nov 25 2018 at 18:54, on Zulip):

Did it have noticeable results on the Rngs output?

Tony Arcieri (Nov 25 2018 at 19:01, on Zulip):

@Shnatsel this code is nothing but side effects, heh

Tony Arcieri (Nov 25 2018 at 19:01, on Zulip):

the RNG was a red herring, it was a bug in the encoding library (subtle-encoding)

brycx (Nov 25 2018 at 19:04, on Zulip):

Ah, I see what you mean.

Tony Arcieri (Nov 25 2018 at 23:16, on Zulip):

I might look into rewriting the Encoding trait to use iterators, so there's no danger of exposing a zero-initialized buffer like what happened here... except for the part where you can't really "construct" an array out of an iterator yet, right? but it'd work for a Vec which does happen to be what was in use here

Tony Arcieri (Nov 25 2018 at 23:16, on Zulip):

I'm trying to keep the core API no_std friendly

Tony Arcieri (Nov 25 2018 at 23:18, on Zulip):

https://github.com/rust-random/rand/pull/643#issuecomment-439114850

Tony Arcieri (Nov 25 2018 at 23:18, on Zulip):

something like let buffer: [u8; 32] = mem::secure_randomized() or thereabouts might be interesting

Tony Arcieri (Nov 25 2018 at 23:19, on Zulip):

possibly with an argument for whether or not to use a blocking or non-blocking CSRNG

briansmith (Nov 26 2018 at 01:26, on Zulip):

@Tony Arcieri Could you please link to the code that uses `debug_assert_eq!

briansmith (Nov 26 2018 at 01:27, on Zulip):

In particular, I am very curious about whether putting `debug_assert_eq!

briansmith (Nov 26 2018 at 01:27, on Zulip):

is effectively equivalent to MSVC's __assume() in release builds, which would be insane!

Tony Arcieri (Nov 26 2018 at 02:14, on Zulip):

well you can probably guess, it was the side-effectful function which actually did the work, which in 20/20 hindsight is uhh, well rather embarrassing

Tony Arcieri (Nov 26 2018 at 02:14, on Zulip):

@briansmith https://github.com/iqlusioninc/crates/pull/126

Tony Arcieri (Nov 26 2018 at 02:14, on Zulip):

at least it's an interesting case study, although "secure rng" is a red herring at this point

Tony Arcieri (Nov 26 2018 at 02:14, on Zulip):

my takeaways were, per @briansmith's suggestion, run tests with --release

Tony Arcieri (Nov 26 2018 at 02:15, on Zulip):

and it would be nice to replace the side effects with something more functional so the Vec in this case is never initialized in a bad (in this case all-zero) state

Tony Arcieri (Nov 26 2018 at 02:16, on Zulip):

which I think is totally doable so long as I'm making a Vec... I want to try to try to change the no_std parts of the Encode and Decode traits to operate on iterators rather than slices

Tony Arcieri (Nov 26 2018 at 02:17, on Zulip):

that way when building a Vec I can just collect() the output, rather than making a Vec of zeroes to begin with

Tony Arcieri (Nov 26 2018 at 02:22, on Zulip):

although back to the "secure rng" topic

Tony Arcieri (Nov 26 2018 at 02:22, on Zulip):

the same thing would be nice for buffers initialized with random data

Tony Arcieri (Nov 26 2018 at 02:22, on Zulip):

which is what I was suggesting above with something like mem::secure_randomized()

Tony Arcieri (Nov 26 2018 at 02:24, on Zulip):

as opposed to initializing some buffer full of zeroes and calling fill_bytes(&mut buf) or what have you to mutate full of randomness as a side effect

Tony Arcieri (Nov 26 2018 at 02:28, on Zulip):

given what just happened, suddenly I'm afraid of side effects

Tony Arcieri (Nov 26 2018 at 02:41, on Zulip):

the more I think about my Encode and Decode traits, the more I think they can be defined in terms of ExactSizeIterator

briansmith (Nov 26 2018 at 04:15, on Zulip):

@Tony Arcieri I thought about that as well, and in fact the rand crate does have an API () -> T that returns a T with a random value, for certain types of T.

briansmith (Nov 26 2018 at 04:17, on Zulip):

I remember I never added one because usually the type of the function would have to be <const N>() -> [u8; N] and we don't have that capability in the type system yet.

Tony Arcieri (Nov 26 2018 at 14:45, on Zulip):

orly

Zach Reizner (Nov 26 2018 at 19:04, on Zulip):

There are other warts, such as the "give me random bytes" syscall being equivalent to reading from /dev/urandom as opposed to /dev/random, and you need the latter if you want to seed a cryptographically secure RNG in user-space. This is exacerbated by the fact that if your program running in a sandbox with no access to /dev/random, there is no way to get seed-quality entropy

@Shnatsel I thought current wisdom around urandom vs random was that one should always use urandom to avoid blockking: https://www.2uo.de/myths-about-urandom

Tony Arcieri (Nov 26 2018 at 19:07, on Zulip):

/dev/random and /dev/urandom are both kind of awful, lol. getrandom() is a lot nicer

Zach Reizner (Nov 26 2018 at 19:08, on Zulip):

@Tony Arcieri because you don't need an fd or the open syscall or does it have some additional security properties?

Joshua Liebow-Feeser (Nov 26 2018 at 19:08, on Zulip):

The former

Joshua Liebow-Feeser (Nov 26 2018 at 19:09, on Zulip):

Check out section 3.5 of this paper: https://www.usenix.org/system/files/conference/hotos15/hotos15-paper-corrigan-gibbs.pdf

Tony Arcieri (Nov 26 2018 at 19:09, on Zulip):

"always use /dev/urandom" covers most bases. then I see "but select on /dev/random to ensure it's readable before you access /dev/urandom and so forth"

Joshua Liebow-Feeser (Nov 26 2018 at 19:09, on Zulip):

It's one of my favorites

Joshua Liebow-Feeser (Nov 26 2018 at 19:09, on Zulip):

Oh and also blocking, yeah

Tony Arcieri (Nov 26 2018 at 19:10, on Zulip):

@Zach Reizner it both eliminates the need to juggle a file descriptor and provides flags with precise semantics so you don't need to resort to cargo cult incantations

Zach Reizner (Nov 26 2018 at 19:10, on Zulip):

Oh, and if an attacker can force a host to use /dev/random many times, they can trigger a DoS to everything else that wants to use /dev/random when the entropy counter goes low.

Tony Arcieri (Nov 26 2018 at 19:11, on Zulip):

I've seen that wedge a KDC in practice :weary:

Joshua Liebow-Feeser (Nov 26 2018 at 19:11, on Zulip):

It's worse than a DoS since many utilities which provide randomness just assume that open("/dev/urandom") always succeeds

Joshua Liebow-Feeser (Nov 26 2018 at 19:11, on Zulip):

And when it fails, they end up reading bytes from a bad FD, getting no actual randomness

Joshua Liebow-Feeser (Nov 26 2018 at 19:11, on Zulip):

It's basically as bad as accidentally reading from /dev/zero

Zach Reizner (Nov 26 2018 at 19:11, on Zulip):

Wow, that's some pretty bad programming. :sad:

Joshua Liebow-Feeser (Nov 26 2018 at 19:11, on Zulip):

Agreed

Joshua Liebow-Feeser (Nov 26 2018 at 19:12, on Zulip):

But such is the world we live in :/

Tony Arcieri (Nov 26 2018 at 19:12, on Zulip):

ln -fs /dev/zero /dev/urandom :smiling_devil:

Zach Reizner (Nov 26 2018 at 19:12, on Zulip):

Agreed

Alex Gaynor (Nov 26 2018 at 21:40, on Zulip):

/dev/random's bizzare notions of entropy have taken down IRS systems before, leading to at least two healthcare.gov outages :-(

Shnatsel (Nov 26 2018 at 22:48, on Zulip):

Last time I checked /dev/random vs /dev/urandom vs hardware RNG debacle was still debated, with each side having valid points :oh_no:

Joshua Liebow-Feeser (Nov 26 2018 at 22:52, on Zulip):

The link that @Zach Reizner posted earlier (https://www.2uo.de/myths-about-urandom) sums it up pretty nicely. TLDR: Once everything is properly initialized, /dev/urandom is just as secure as /dev/random.

Joshua Liebow-Feeser (Nov 26 2018 at 22:52, on Zulip):

The problem is that, before things are initialized, their behavior is different in confusing and dangerous ways.

Alex Gaynor (Nov 26 2018 at 23:04, on Zulip):

Which getrandom addresses. The re-seeding behavior of /dev/random is the pointless part.

Joshua Liebow-Feeser (Nov 26 2018 at 23:07, on Zulip):

Right.

Joshua Liebow-Feeser (Nov 26 2018 at 23:08, on Zulip):

(well, pointless under the assumption that the system is secure; in practice, it's possibly useful as a backstop against attacks that manage to learn some of the internal state of the PRNG)

Alex Gaynor (Nov 26 2018 at 23:12, on Zulip):

Err, sorry I shouldn't have said re-seeding. Both urandom and random reseed, it's the blocking on "low entropy" that's nonsense.

Joshua Liebow-Feeser (Nov 26 2018 at 23:15, on Zulip):

Ah, right. The idea that, once properly initialized, you can dip below the entropy threshold and detect this with code is unfounded.

Joshua Liebow-Feeser (Nov 26 2018 at 23:15, on Zulip):

The entire point of a backstop against insufficient entropy is that we have no formal characterization of how that might happen, which is why we believe these systems to be theoretically sound in the first place.

briansmith (Nov 26 2018 at 23:33, on Zulip):

My favorite problem with /dev/urandom and /dev/random was in some code that closed all open file descriptors manually at a certain point during startup (looped from fd 0 until it got EBADFD from close()), not expecting any libraries to have opened files. Then it opened another file that reused that file descriptor, and the PRNG started reading from that file and nobody noticed for a LONG time.

Joshua Liebow-Feeser (Nov 26 2018 at 23:34, on Zulip):

Oh wow that's juicy.

Alex Gaynor (Nov 26 2018 at 23:35, on Zulip):

Python fstats the file descriptor and checks that (ino, dev) hasn't changed because of this garbage.

Joshua Liebow-Feeser (Nov 26 2018 at 23:35, on Zulip):

What a beautiful hack. How sad that that's necessary.

briansmith (Nov 26 2018 at 23:37, on Zulip):

Alex, I'm always impressed with how the Python stuff adds these kinds of workarounds. I myself would love to turn off the fallback to /dev/urandom in my code to avoid the situation completely.

Joshua Liebow-Feeser (Nov 26 2018 at 23:40, on Zulip):

I presume that once the last version of Linux w/o the syscall isn't supported by Python, that'll be a viable option?

Alex Gaynor (Nov 26 2018 at 23:41, on Zulip):

I want to kill non-getrandom paths for pyca/cryptography. I should look at the metrics and figure out how impossible that is.

Tony Arcieri (Nov 28 2018 at 20:27, on Zulip):

just use RDRAND [img:trollface]

Tony Arcieri (Nov 28 2018 at 20:28, on Zulip):

(literally what I'm doing in SGX, but in for a penny, in for a pound there)

Joshua Liebow-Feeser (Nov 28 2018 at 20:28, on Zulip):

Oh I think I spot your problem:

Joshua Liebow-Feeser (Nov 28 2018 at 20:28, on Zulip):

You're using SGX.

Tony Arcieri (Nov 28 2018 at 20:28, on Zulip):

kinda the way I feel about, you know, the CPU in general, but using RDRAND from regular ol' Linux userspace is bad for virtue signaling

Tony Arcieri (Nov 28 2018 at 20:28, on Zulip):

haha

Tony Arcieri (Nov 28 2018 at 20:28, on Zulip):

at least I'm using SGX to talk to HSMs

Joshua Liebow-Feeser (Nov 28 2018 at 20:28, on Zulip):

fair

Tony Arcieri (Dec 12 2018 at 17:09, on Zulip):

is anyone interested in working on an RFC for stabilizing an "OS rng" lang item and associated std/core API? https://github.com/rust-random/rand/issues/648

Alex Gaynor (Dec 12 2018 at 21:55, on Zulip):

Hmm, why a lang item vs. just an in-tree implementation?

Joshua Liebow-Feeser (Dec 12 2018 at 22:07, on Zulip):

Can you summarize the (long) discussion on that issue? Was the consensus that we should move to std/core?

Tony Arcieri (Dec 12 2018 at 22:16, on Zulip):

@Alex Gaynor so if you're on an RTOS or doing bare metal programming on an embedded device, you can leverage the available platform RNG

Joshua Liebow-Feeser (Dec 12 2018 at 22:17, on Zulip):

Why does that require it to be a lang item?

Tony Arcieri (Dec 12 2018 at 22:17, on Zulip):

or when I was doing, say, Thales CodeSafe, talk to the SEE/TEE/TPM via their proprietary messaging protocol (wrapped up in SEElib) and get random numbers that way

Alex Gaynor (Dec 12 2018 at 22:17, on Zulip):

Hmm, so that'd be applicable to a core solution. I think my (weakly held!) opinion is that we ought to start with a simple std API for tier-1 platforms.

Tony Arcieri (Dec 12 2018 at 22:17, on Zulip):

@Joshua Liebow-Feeser well first, because the platform abstraction is already in-tree

Tony Arcieri (Dec 12 2018 at 22:17, on Zulip):

and there is no mechanism by which you can provide an alternative implementation in an embedded context

Joshua Liebow-Feeser (Dec 12 2018 at 22:17, on Zulip):

Yeah, I agree with Alex. There's enough complexity to get right even for that.

Tony Arcieri (Dec 12 2018 at 22:18, on Zulip):

even if it's, say, Thales providing a more-or-less getrandom()-style API

Joshua Liebow-Feeser (Dec 12 2018 at 22:18, on Zulip):

E.g., what's the blocking semantics? On Linux, what do we do if getrandom isn't available? What do we do if a call fails? What do we do if getrandom isn't available and /dev/urandom can't be opened because we're out of FDs? etc.

Tony Arcieri (Dec 12 2018 at 22:18, on Zulip):

getrandom() is parameterized around the blocking semantics

Joshua Liebow-Feeser (Dec 12 2018 at 22:19, on Zulip):

I know; I'm just saying that we have to nail all of those details down

Tony Arcieri (Dec 12 2018 at 22:19, on Zulip):

if it doesn't exist, you need to "polyfill"

Tony Arcieri (Dec 12 2018 at 22:19, on Zulip):

which is possible with /dev/random and /dev/urandom on Linux

Joshua Liebow-Feeser (Dec 12 2018 at 22:19, on Zulip):

My ideal API is one which a) blocks completely and, b) aborts the process if there's ever an error.

Tony Arcieri (Dec 12 2018 at 22:19, on Zulip):

in a somewhat hackish manner

Joshua Liebow-Feeser (Dec 12 2018 at 22:20, on Zulip):

I know. I'm just saying that it's complex enough that it's probably worth its own RFC. We have to do the same thing on all of the tier-1 platforms.

Tony Arcieri (Dec 12 2018 at 22:20, on Zulip):

select-for-read on /dev/random will block until the kernel entropy pool has been seeded (I think)

Joshua Liebow-Feeser (Dec 12 2018 at 22:20, on Zulip):

select-for-read on /dev/random will block until the kernel entropy pool has been seeded (I think)

I believe that's correct, although I'd need to double-check.

Tony Arcieri (Dec 12 2018 at 22:20, on Zulip):

there's a couple things going on here

Tony Arcieri (Dec 12 2018 at 22:20, on Zulip):

one is a stable std or core API

Tony Arcieri (Dec 12 2018 at 22:20, on Zulip):

another is a lang item

Tony Arcieri (Dec 12 2018 at 22:20, on Zulip):

I'd like both

Joshua Liebow-Feeser (Dec 12 2018 at 22:21, on Zulip):

I agree. However, I think Alex's point is that we can do the former first and then add a lang item later.

Tony Arcieri (Dec 12 2018 at 22:21, on Zulip):

but I'd be interested in working on either

Tony Arcieri (Dec 21 2018 at 14:16, on Zulip):

https://github.com/dhardy/rfcs/blob/system-random/text/0000-system-random.md

Joshua Liebow-Feeser (Jan 29 2019 at 01:42, on Zulip):

Has this been turned into a PR on the RFCs repo yet?

Tony Arcieri (Jan 29 2019 at 01:52, on Zulip):

I don't think so... this is the last update on it I see https://github.com/rust-random/rand/issues/648#issuecomment-451286240

Joshua Liebow-Feeser (Jan 29 2019 at 01:52, on Zulip):

mmmmk

Gerardo Di Giacomo (Jan 30 2019 at 23:37, on Zulip):

ThreadRng is considered, by documentation, a safe CSPRNG, however I've always been wary of user-space RNGs (and my understanding is that threadrng is user-space). is it actually safe to use or it's preferable/better/mandatory to use OsRng ?

Tony Arcieri (Jan 31 2019 at 00:17, on Zulip):

I share these opinions. There is work on a getranndom crate which does what I consider the right thing, which is "get randomness from the platform" (i.e. kernel) https://github.com/rust-random/getrandom

Tony Arcieri (Jan 31 2019 at 00:17, on Zulip):

whenever they release it I will switch over (from rand_os)

Tony Arcieri (Jan 31 2019 at 00:17, on Zulip):

my main concern has been JitterRng

Tony Arcieri (Jan 31 2019 at 00:17, on Zulip):

which... ugh

Tony Arcieri (Jan 31 2019 at 00:18, on Zulip):

maybe a year ago I suggested Randen

Tony Arcieri (Jan 31 2019 at 00:19, on Zulip):

and was told "it's too new to trust!" sure fine

Tony Arcieri (Jan 31 2019 at 00:19, on Zulip):

but then all of my stuff started breaking because of rand crate changes which manifested as JitterRng breakages

Tony Arcieri (Jan 31 2019 at 00:19, on Zulip):

and I'm like "what the hell is this"

Tony Arcieri (Jan 31 2019 at 00:20, on Zulip):

I spent a few days learning about it and saw it was a Havaged-alike "CryptoRng" which creates "secure randomnness" by doing a bunch of ad hoc branching on attacker-influenceable values

Tony Arcieri (Jan 31 2019 at 00:21, on Zulip):

and then I started reading papers and concluded that entire approach as no basis in cryptography research

Tony Arcieri (Jan 31 2019 at 00:21, on Zulip):

people call it "TRNG" but that's... not what TRNG means

Tony Arcieri (Jan 31 2019 at 00:21, on Zulip):

TRNGs are hardware devices

Tony Arcieri (Jan 31 2019 at 00:22, on Zulip):

so anyway tl;dr: Randen is a much better candidate for a CryptoRng than JitterRng

Shnatsel (Jan 31 2019 at 13:43, on Zulip):

Randen has official Rust bindings too, wow.
The dependency on hardware-accelerated AES for performance is not great though. Not all x86_64 CPUs have it, which means it cannot be used as a default without runtime RNG selection, and you need some kind of fallback anyway.

Tony Arcieri (Jan 31 2019 at 18:45, on Zulip):

yes the joys of AES-NI detection and fallback

Gerardo Di Giacomo (Jan 31 2019 at 20:13, on Zulip):

so anyway tl;dr: Randen is a much better candidate for a CryptoRng than JitterRng

thanks for the comments. but what about threadrng? :D

Gerardo Di Giacomo (Jan 31 2019 at 20:13, on Zulip):

I've been switching to OsRng

Gerardo Di Giacomo (Jan 31 2019 at 20:15, on Zulip):

but I (security) have to debate with engineering as the documentation says that threadrng is safe to use

Tony Arcieri (Feb 01 2019 at 16:12, on Zulip):

@Gerardo Di Giacomo well I think ideally it'd be safe to use, but isn't quite yet, IMO. there's a lot that's in flux right now

Tony Arcieri (May 21 2019 at 07:01, on Zulip):

https://github.com/rust-random/getrandom/issues/21

Last update: Nov 11 2019 at 21:55UTC