Skip to content
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

Security Issue: KDF max iterations is too low #589

Closed
sneak opened this issue Nov 1, 2019 · 31 comments
Closed

Security Issue: KDF max iterations is too low #589

sneak opened this issue Nov 1, 2019 · 31 comments

Comments

@sneak
Copy link

sneak commented Nov 1, 2019

if(KdfIterations.Value < 5000 || KdfIterations.Value > 2_000_000)

From bitwarden/jslib#52 :

However, that workaround is specifically prohibited by Bitwarden, restricting it to 2M iterations. Modern SHA256 hardware can do 22,200,000,000 hashes per watt-second, so a single unit operating at 1000W can bruteforce 11,100,000 passwords per second with the maximum iteration count allowed. The default iteration count is much less, allowing for practical and efficient dictionary attacks with hardware.

@sneak
Copy link
Author

sneak commented Nov 1, 2019

Note that when changed, the frontend validation logic will need to be changed here as well:

https://github.com/bitwarden/web/blob/9c2f1285855d0710a4b90f0de53619cc65a108bf/src/app/settings/change-kdf.component.html#L32

@92VV3M42d3v8
Copy link

Minimum iteration should be at least 50,000.

@Mart124
Copy link
Contributor

Mart124 commented Dec 13, 2019

allowing for practical and efficient dictionary attacks with hardware.

"dictionary" is the bad word here :)
IMO we should rather think about enforcing master password strength.

@rodehoed
Copy link

Seems there is an CVE now for this issue? Any news on this one?

@sneak
Copy link
Author

sneak commented Dec 18, 2019

Who did that? It wasn't me.

@sneak
Copy link
Author

sneak commented Dec 18, 2019

@92VV3M42d3v8
Copy link

So when are you going to implement Argon2

@fallmake
Copy link

Besides a significant increasing of the current max iterations limit, please think about the possibility of adding Argon2d or Argon2id as an experimental feature for self-hosted environments via setting if the "Argon2 is not in a standard/too young/unproven and unsuitable for this cross-platform project" (yet) point still stands.

@cscharf
Copy link
Contributor

cscharf commented Sep 22, 2020

Is there a recommended max suggested? If there's a new industry standard recommendation for KDF iterations for SHA256 when using a master password combined with a private key to create a heck-of-a-lot of bits of entropy feeding it. So you can't take master password length + complexity alone, you also have to factor the user's private key when calling this a true vulnerability. Either way, we'll consider testing and validating a new max iteration count if someone can toss out a number they feel is valid (btw, most other password managers on the market land ~ 100k iterations).

@sneak
Copy link
Author

sneak commented Sep 23, 2020

The numbers you need to raise to fix this vulnerability are the min and default, or the default configuration will remain vulnerable.

Modern hardware can do ~22 billion sha256 per watt-second, as I wrote last October.

Do you need me to do the basic arithmetic for you or is this an admission of no cryptography engineers on your team?

@cscharf
Copy link
Contributor

cscharf commented Sep 23, 2020

The title of the report is: "KDF max iterations is [sic] too low", hence why I asked what you felt a better max number would be, so if the issue is the min number, that's different.

Our default is 100,000 iterations, the Min allows for higher performance at the user's discretion but the key length combined with the password still makes this relatively secure.

We use GitHub issues as a place to track bugs and other development related issues. The Bitwarden Community Forums has a section for submitting, voting for, and discussing product feature requests like this one.

Please sign up on our forums and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.

This issue will now be closed. Thanks!

@cscharf cscharf closed this as completed Sep 23, 2020
@Myer
Copy link

Myer commented Sep 23, 2020

Regarding the min number and "official" recommendations...

RFC8018 (https://tools.ietf.org/html/rfc8018#section-4.2) mentions it follows the recommendations of NIST 800-132 (https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf) which says:
A minimum iteration count of 1,000 is recommended. For especially critical keys, or for very powerful systems or systems where user-perceived performance is not critical, an iteration count of 10,000,000 may be appropriate.
I guess you can consider your master password as an "especially critical key".

But note that's from December 2010.

The latest guidelines are NIST 800-63B (https://pages.nist.gov/800-63-3/sp800-63b.html#sec5) which are latest updated on March 2020. They say:
For PBKDF2, the cost factor is an iteration count: the more times the PBKDF2 function is iterated, the longer it takes to compute the password hash. Therefore, the iteration count SHOULD be as large as verification server performance will allow, typically at least 10,000 iterations.

The OWASP Password Storage Cheat Sheet (https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2) says the same:
The work factor for PBKDF2 is implemented through the iteration count, which should be at least 10,000 (although values of up to 100,000 may be appropriate in higher security environments).

So I would suggest to at least increase the minimum to 10,000.

@sneak
Copy link
Author

sneak commented Sep 24, 2020

@cscharf The max is too low to allow any secure setting to be configured today. 2M iterations is wholly insufficient. The max needs to be changed, of course.

But, it turns out, just upping the max and leaving the default alone will still result in an insecure configuration for almost every user. You'll have to fix the max, too, of course, but the default will need to be changed.

Looks like I need to do the math for you.

22 Ghash/watt/sec means that a modest home attacker with, say, 1kW of power (I run more than this in my residential garage) can do 22 Thash/sec.

(26 * 2 + 10)  = 62  // the alnum space

62 ^ 8 = 218,340,105,584,896 // 8 character password

218,340,105,584,896 / 22,000,000,000,000 = 9.9245502539 // ten seconds

so, with an iteration count of 1, you're looking at 10 seconds to attempt the entire 8 character a-zA-Z0-9 alphanumeric space. Double it if Bitwarden's using PBKDF2-HMAC-SHA256 (I haven't looked at the code lately, I reported this issue a year ago and don't recall what it does). Let's assume it is, so figure 20 seconds for that password space and one iteration.

20 * 5,000 iterations = 100,000 seconds. Just over a day.

Up that to 2M iterations, the max permitted, and it's still under 18 months. Recall that this is using only 1kW to perform the attack. Someone temporarily renting say, 10kW of hashpower (still a very small amount of resources) to perform the attack could get that down to 47 days.

Either you need to use a much better PRF inside of PBKDF (like Argon2, which has been suggested in another thread), or you need to up the default to approximately 10-100x what the max currently is today. This necessitates increasing the max, naturally, but the minimum should also be 20M or greater to avoid people shooting themselves in the foot.

My official recommendations for the settings, given it's 2020:

min: 50M
default: 200M
max: 5000M

Note that these are actually rather risky values still, but are chosen because I doubt using such large values would allow logins in a reasonable period of time on some of the Bitwarden client devices. The correct fix is really to use a modern algorithm, but you've already decided not to do that.

As we've learned from the other thread, though, the encryption in Bitwarden is a farce anyway, so I assume this is going to remain closed and unfixed. Be well and good luck!

@sneak
Copy link
Author

sneak commented Sep 24, 2020

Note that the OWASP recommendation of iteration count is bad: PBKDF2 is a function that is designed to iterate an internal pseudorandom function (PRF), such as SHA256 or HMAC-SHA256.

The underlying PRF's speed is what determines the iteration count security level, not the iteration wrapper PBKDF2 itself. Saying "at least 10k, or maybe 100k" when the context is only "PBKDF2" is meaningless without also specifying the PRF that PBKDF2 is iterating.

https://en.wikipedia.org/wiki/PBKDF2

You guys should know all this already, because you're in the password manager business. It's a bummer you don't.

@Mart124
Copy link
Contributor

Mart124 commented Sep 24, 2020

@cscharf these iteration settings could perhaps be enforced through the new policies, it would allow an organization to improve its users' security (and its own security...).
Thank you 👍

@ThunderSon
Copy link

I have gone through the festivities of 3 issues being raised and thrown around on Bitwarden.
@sneak this is mostly for you, and for the community to understand some of the discussions.

So let me first start with the comment on the OWASP CS on Password Storage:

  • The PRF is mentioned just one sentence above the iterations.
  • The iterations count is the lowest limit 10k, with the security recommendation of 100k by NIST. If you notice, the CS specifies when to use this function. Quoting:

PBKDF2 is recommended by NIST and has FIPS-140 validated implementations. So, it should be the preferred algorithm when these are required.

If you need more than 100k, then by all means, don't refer to a cheat sheet for DEVELOPERS. If you're a cryptography expert, this CS is not for you. It's for the developers without a cryptography expert. Throwing random recommendations to 50M as a minimum value that is supposedly running on a RPi or an old mobile phone is not something that any of the security community will take or accept.

The CS recommends the Bcrypt function because it's the simplest to implement for developers, it's robust, and it's secure. You can implement argon2? Go ahead, we say that as well across the CS. Mess up one variable in argon2 and you have all hell wreaking havoc in your system (either by DOSing it, or by being too fragile and actually weaker than bcrypt)

Second, the CVE you keep referencing, and I quote:

The Bitwarden server through 1.32.0 has a potentially unwanted KDF.

The KDF in discussion is PBKDF2-SHA256, which is a standard. I have no idea who created that CVE, but it shows how ill-informed they are. "Potentially unwanted". Just read it. What is this? Why is it unwanted?
Or are they maybe talking about the iterations count? Well, either ways, I don't know, and neither does that CVE. Stop referencing it everywhere.

Third off, stop throwing shade on Bitwarden like you own shares in this software!
This is open source, stop commanding and leading a FUD on them. This is disgusting. I get it, they can improve their security posture and you want them to. That's really amazing, honestly.
What's not amazing is how you're talking and fighting. @michaelsmoody told you several times to check and support what they're doing, EVERYONE actually did. You simply ignored their comments because you want the team to handle it like this is a breach in their solution (If you spoke with Michael in private, I take this back, yet I did not see a reply anywhere that you did). You don't like it, and you don't approve of their responses? Great, go use another tool that does. This is open source, not your backyard.

Oh, and let's stop talking about 8 chars passwords too. That's a whole different issue in itself. If someone sets a password to 8 chars then no PHF is going to save them. How about we start talking with 12-16 chars? That sounds much better as a base.

Disclaimer 1: I was one of the reviewers of the password storage CS, and we took the references and words of the hashcat team on how this should be worded and written. If you want the CS to be updated, go and open an issue with a recommendation. Stop throwing beef randomly everywhere. Have some respect for the HUGE work being put everywhere. Your comments are technical and great, the delivery is not.

Disclaimer 2: I am not affiliated with bitwarden nor care on what they actually action on this. I have just seen this being thrown around in bad ways. Open issues (that provide what you'll be doing) and open PRs, help them improve if you actually care.

@sneak
Copy link
Author

sneak commented Sep 26, 2020

I am a bitwarden user, so I have quite a bit more at stake from their poor practices than if I were just a shareholder in the company.

Regardless, they are more interested in having a shiny product than building secure cryptography software, so I’ve already unsubscribed from the thread, will be migrating my own use away, and migrating the client fleets I’ve steered toward them in the past away. Please don’t mention me in these threads any longer.

@gidoBOSSftw5731
Copy link

I would understand hesitance if this was for adding argon, but why are you guys so hesitant to increase the maximum iterations (or I mean, remove it too, if you prefer). This is (more or less) as hard as changing a few variables (or one if you make this well). I really like the idea of a self hosted password manager but using pbkdf2 is an unexcuseable compromise when you then refuse to do anything about securing it.

@polarathene
Copy link

polarathene commented Dec 27, 2020

Up that to 2M iterations, the max permitted, and it's still under 18 months.

@sneak ((((62^8) / (22 * 10^12)) * 2) * (2 * 10^6)) / (60 * 60 * 24 * 30) = ((password / hashrate(22 trillion/sec)) * 2 * 2 million) / seconds in a month = ~15.32 months. (It's technically half the time on average btw when exhausting a keyspace via brute force)

  • 62^9 - Just one more character of added entropy and that calculation results in 950 months. (~53.6 bits)
  • 95^8 - The inclusion of symbols, 465 months. (~52.6 bits)
  • 7776^4 - Using four words from the EFF diceware list, 256 months. (~51.7 bits)

I found it a bit convenient that you chose only alphanumeric and a length of 8. This is your master password you're concerned about and you want to secure it with what would be considered a weak password with low entropy? (~47.6 bits)

I wouldn't be surprised if it's much weaker by being a single word all in lowercase or matching one of the top 1M passwords. The issue isn't on BitWarden, it's on your own security practices.

Recall that this is using only 1kW to perform the attack. Someone temporarily renting say, 10kW of hashpower (still a very small amount of resources) to perform the attack could get that down to 47 days.

How much is that going to cost in $? How much resources are being allocated by this attacker for 47 days to solely attack your password? Are they likely to perform an attack like this for 47 days on numerous users, or are they specifically targeting you?

If you assume the likelihood of that is high or this is a well funded attacker with no concerns about wasting resources then what is the best way for YOU to defend against this attacker? Is it:

A) Use a weak password and pray for open-source software to use sufficient iterations to my satisfaction with their KDF.
B) Use a stronger password with high entropy.

You chime in here with a bunch of info to push your interests for A, and choose to do so rudely. Choose B and be on your way.

Such an attacker has cheaper avenues to attack you with. It would cost them less to:

  • Send someone directly to you and beat the password out of you.
  • They've already compromised you to gain access to your master password hash, what else can they do? With poor security practices on your end, there's a good chance you're vulnerable to viruses, malware, social engineering, etc.

This necessitates increasing the max, naturally, but the minimum should also be 20M or greater to avoid people shooting themselves in the foot.

I have an i5-6500 Intel CPU. That's 4/4 cores/threads. I just did 20M iterations (small rust program running PBKDF2-HMAC-SHA256) and it took 17 seconds. Your advised minimum of 50M iterations takes 44 seconds long. So by default you're wanting to impose about 3 minutes of wait time from my password to be hashed before I can proceed?..75 minutes for maximum?

You need to account for UX here. You're pointing fingers that iterations are too low and thus your weak password can be attacked in 18 months or less by your given math. As we've clarified, using a more secure password will give you the same benefits you're wanting without imposing massive delays on other users UX.

2 million iterations takes about 2 seconds with my CPU, 90ms for 100k iterations. I imagine many self-hosted deployments of BW are on weaker/restricted hardware where that'd take a bit longer. This is perfectly fine default for good UX while the KDF is still doing it's part to increase security effectively, it alone should not be relied on to secure your password, add more entropy to your password.

As we've learned from the other thread, though, the encryption in Bitwarden is a farce anyway

I hope those that land on here don't take this guy seriously. He's ranted about so much already trying to make BW look bad when they're actually doing well.

Argon2id would be good as a configurable alternative, perhaps even scrypt if the justification for adopting argon2 is not going to budge. The same logic applies though, KDF slows the hashing process down which discourages attackers from bruteforce guessing.

You can keep extending the time to hash, or you can increase the strength of your password. Extending the time is beneficial with many users where password strength may be poor, but this is your master password just for you, on a self-hosted service. You're going to get so much more security out of a stronger password than relying heavily on a KDF slowing an attacker down.

For those not in the know:

  • bcrypt is a common slow hash, but you can only extend the time to hash. It has a input limit of 72 bytes (not exhaustive, you'll input a string, but anything beyond 50-ish bytes depending on implementation may be discarded (and definitely after 72)), note that unicode like emoji characters may look like a single value, but can quite easily use 17 bytes to represent.
  • scrypt is meant to be an improvement by increasing memory required (not by much for a user, but can seriously hamper attacker hardware), but it's memory size is tied to the computation time, they don't scale independently. Attackers can lower the memory required but this greatly raises the computation time, so it's not faster/easier other than possible to attack at scale.
  • argon2 has several modes as well as tunables. It's the least straight-forward to configure, a user can't just up a number or have some slider of min vs max strength. You can adjust the iterations and memory usage separately, along with parallelism, where you tune it to be difficult for attackers to benefit, but not hampering your legit usage.

@polarathene
Copy link

Mess up one variable in argon2 and you have all hell wreaking havoc in your system (either by DOSing it, or by being too fragile and actually weaker than bcrypt)

@ThunderSon Like PBKDF2 not being setup for 1 iteration by default and leaving it up to the user to suss out, I would imagine an argon2 implementation would also have a reasonable default. If the user wants to tweak parameters because they know better (or think they do), that's on them.

You can already bump PBKDF2 up to 2M that is supported and DoS the service by locking up CPU? What were you seeing as a different attack with argon2, memory?

Correct approach would be to have some rate limiting or other defenses restricting access to the service. I don't see that as a compelling reason against argon2 support (but I do agree not to demand and expect it, if someone really wants it bad enough they can contribute or sponsor the feature).

Regarding weaker than bcrypt, that's 4KiB RAM, only difference then would be time when comparing no? That's something BW could provide via the UI as feedback if the user isn't experienced enough to make that choice, but at the same time they shouldn't be messing with such parameters if they don't know what they're doing.

bcrypt itself has it's own issues (though I doubt many would run into them, I did see some BW community thread talking up how great emojis in the master password would be for boosting entropy, which would be misleading).

Throwing random recommendations to 50M as a minimum value that is supposedly running on a RPi or an old mobile phone is not something that any of the security community will take or accept.

I think it was pretty evident they didn't even consider the UX impact of that many iterations and were only thinking of securing their weak password from an attack which made no sense.

@polarathene
Copy link

but why are you guys so hesitant to increase the maximum iterations

@gidoBOSSftw5731 Do you believe it is the right thing to do and makes the security better in the correct way?

At 2 million iterations, you'll be waiting ~2 seconds depending on machine performing the hashing. You would like to further increase that? The user advised a max of 5 billion iterations, which on my machine would take 75 minutes.

If you're not able to grasp KDF iterations purpose, then you would see the maximum as the most secure option and possibly opt for that. Only to find that BW has become unresponsive for over an hour and doesn't seem to be liking your master password, so you raise an issue on github about it being buggy.

2 million is plenty. The complaining user with all their math and statistics to sound convincing at making BW look at fault was clearly describing the time and resources required to attack a very weak password.

Do you think a 62^8 keyspace password (alphanumeric upper/lower case, no symbols, 8 characters long) is a good choice for your master password that secures all your other passwords? My response to that user details small differences that dramatically change the time to attack based on their logic.

It's not weak number of iterations here, it's a weak password. Should BW encourage using weak passwords? Or would it be better to educate such a user instead? (not saying that they do, but it's a much better fix to have that setting mention if the max value isn't sufficient, please consider a stronger password as it'll be more effective).

I really like the idea of a self hosted password manager but using pbkdf2 is an unexcuseable compromise when you then refuse to do anything about securing it.

What aren't they doing that they should? I think I've just conveyed why adding more iterations isn't the right approach when you want to secure something.

@gidoBOSSftw5731
Copy link

I don't believe more iterations is going to be the solution to all of this, but instead moving to something that does not have the hardware to make it so easy to brute force, like argon2, scrypt, or other similar password algorithm that has more precautions than pkdbf. My argument on not capping maximum iterations is "if you aren't going to implement a better key derivation function, atleast let us secure the current implementation more." This as an artificial bottleneck just seems unnecessary, if you want security you'll wait 10 sec or whatever for your password and if not you probably don't even know what pkdbf is since you're s normal user.

@polarathene
Copy link

instead moving to something that does not have the hardware to make it so easy to brute force, like argon2, scrypt, or other similar password algorithm that has more precautions than pkdbf.

It's not easy to brute force though, just don't use a weak password to begin with?

Argon2 can be configured nicely but if your password is password it's added no real advantage to security vs 2 million iterations of PBKDF.

This as an artificial bottleneck just seems unnecessary, if you want security you'll wait 10 sec or whatever for your password

2 seconds per attempt on my PC is slow. The math above shows it taking many months with more capable hardware to exhaust the weak password key space.

It's already not worth attacking that unless you're being targeted directly. Getting your password may be cheaper and faster by alternative approaches, and if you honestly care about your password strength security beyond what 2 million iterations is providing you, then simply make a slightly stronger password, it'll be far more effective without degrading your UX even further....nobody wants to wait 75 mins to hash the master password.

This is like arguing 2048-bit vs 4096-bit RSA certificates when 2048-bit RSA is very secure with no tangible benefit really to use 4096-bit, especially as a default, some even thought 8192-bit RSA was more secure and worthwhile without valid justification of how.

@Mart124
Copy link
Contributor

Mart124 commented Mar 16, 2021

Seems like there are 2 places where KDF iterations can be tuned.

For the master password itself :

kdfInformation = new UserKdfInformation
{
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 100000
};

And also here :

public static IdentityBuilder AddCustomIdentityServices(
this IServiceCollection services, GlobalSettings globalSettings)
{
services.AddSingleton<IOrganizationDuoWebTokenProvider, OrganizationDuoWebTokenProvider>();
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 100000);

Seems like it defines the number of iterations for the Identity server.
Could one explain what this hasher is exactly used for ?
What type of information is hashed, and how long does it remain (to appreciate whether or not the default 100k value could be enough here) ?

Many thanks 👍

@cscharf
Copy link
Contributor

cscharf commented Mar 18, 2021

Sorry @Mart124 , I'll do my best to answer these:

server/src/Api/Controllers/AccountsController.cs

This is simply the "Default" when a KDF is not set for the user's email address passed in during pre-login. This code will return the default if the user hasn't overridden them themselves in the settings.

server/src/Core/Utilities/ServiceCollectionExtensions.cs

This is the default password hasher settings used when comparing password hashes server-side for things like validation when changing your email address (server/src/Core/Services/Implementations/UserService.cs; line 519) or accessing a password protected Send (server/src/Core/Services/Implementations/SendService.cs; line 185).

@Mart124
Copy link
Contributor

Mart124 commented Mar 19, 2021

Thank you again @cscharf, perfectly clear, as usual 👍

@cscharf these iteration settings could perhaps be enforced through the new policies, it would allow an organization to improve its users' security (and its own security...).
Thank you 👍

If I may, what about this idea ?
Or through an option in global.override.env configuration file.
We would then be able to tune KDF iterations for users registering on self-hosted servers, improving global security...
Would be nice if this could be added in your backlog, with some kind of "security priority".
Thank you again !

@cscharf
Copy link
Contributor

cscharf commented Mar 19, 2021

Would be nice if this could be added in your backlog, with some kind of "security priority".

Done!

@Mart124
Copy link
Contributor

Mart124 commented Apr 15, 2022

Just a tiny ping here, to ask for some news regarding this security feature :)
Many thanks @cscharf 👍

@cscharf
Copy link
Contributor

cscharf commented Apr 18, 2022

@Mart124 , this is still in our backlog under Enterprise Policy: Enforce minimum and default KDF, however is not currently prioritized for development. Tagging @bitwarden/dept-product and @dwbit on that, perhaps the community forums would be a better place to raise it for a broader audience.

@Mart124
Copy link
Contributor

Mart124 commented Apr 18, 2022

Thank you very much @cscharf, understood, good news is that it's still in your backlog, perfect :) 👍

@ecki
Copy link

ecki commented Jan 31, 2023

Note that the OWASP recommendation of iteration count is bad: PBKDF2 is a function that is designed to iterate an internal pseudorandom function (PRF), such as SHA256 or HMAC-SHA256.

OWASP specified different recommendations per hash, however the numbers seems to be not very rigidly derived and they are targeted do to usage in online authentication databases, not for offline crypto vault KDF. So you might say, they are bad:

PBKDF2-HMAC-SHA1: 1,300,000 iterations
PBKDF2-HMAC-SHA256: 600,000 iterations
PBKDF2-HMAC-SHA512: 210,000 iterations

But those are roughly 0,1 - 1s on most clients or GP servers, so it’s a good start for an default (but the maximum should be allowed higher, especially with expected growth).

2Mio HMACSHA256 iterations however looks reasonable. If you have higher demands, don’t use a online service login with the same password. -> server does not care about DOS.

BTW while PBKDF2 image is rather bad, numbers from the field don’t look that bad. Even with most recent High End CPUs the most optimized hashcat implementation displays bitwarden as only one of few in the kH/s range mit modest iterations (1/20 of max!)

RTX 4090

  • Hash-Mode 23400 (Bitwarden) [Iterations: 99999]
    Speed.# 1.........: 92022 H/s (58.21ms) @ Accel:8 Loops:1024 Thr:512 Vec:1

It might not be so positive for purpose build hardware, but even expensive top of the line hardware does not a shame PBKDF2. Source https://gist.github.com/Chick3nman/32e662a5bb63bc4f51b847bb422222fd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests