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

Self-signed certificate support #106

Open
endervad opened this issue Aug 17, 2021 · 67 comments
Open

Self-signed certificate support #106

endervad opened this issue Aug 17, 2021 · 67 comments
Labels
hacktoberfest Issues available for Hacktoberfest

Comments

@endervad
Copy link

endervad commented Aug 17, 2021

I've set up a self-signed certificate on my Jellyfin server and installed it also as a trusted root CA on my devices. It works fine without security warnings on Chrome (both Windows and Android) and on Android apps, like official Jellyfin and Gelli, except Finamp. When I try to connect to my server in Finamp 0.5.1, I get
HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:359))

Certificate extensions:

X509v3 extensions:
    X509v3 Key Usage: critical
        Digital Signature, Key Encipherment
    X509v3 Extended Key Usage:
        TLS Web Client Authentication, TLS Web Server Authentication
    X509v3 Subject Alternative Name:
        DNS:<redacted>
    X509v3 Basic Constraints: critical
        CA:TRUE
    X509v3 Subject Key Identifier:
        <redacted>
@jmshrv
Copy link
Owner

jmshrv commented Aug 17, 2021

Try this APK, based off the allow-bad-certs branch:
app-arm64-v8a-release.zip
app-armeabi-v7a-release.zip
app-x86_64-release.zip

This just allows all bad certificates, so I won't put this in the main branch. Just wondering, why don't you just use Let's Encrypt to get a proper certificate?

@endervad
Copy link
Author

This one works, thanks :)
I have a probably rather unique case: I've got a DNS name to my public IP from ISP. It's not part of contract, and I'm not even sure that it was intended, but I don't pay for it so might as well use it as is.

@endervad
Copy link
Author

endervad commented Aug 18, 2021

Upon further usage of allow-bad-certs version it seems like I was careless to assume, that if I can see the library, everything works. Neither downloads, nor playing songs work.
In case of downloads there's only a single PlatformException((0) Source error, null, null, null) in Main Thread > AudioServiceHelper with null stack trace. All downloads fail almost instantly one by one after selecting where to save them (I gave storage permission to the app and I'm saving to default Internal storage).
And in playing there's the exact same handshake exception for every attempt to play any song in Audio Service > MusicPlayerBackgroundTask with null stack trace.

@jmshrv
Copy link
Owner

jmshrv commented Aug 18, 2021

Oh yeah, I forgot about native connections (the player and downloader are both native code). I'll look and see if there's any way of allowing bad certs.

About your setup though, are you connecting through an IP or a domain name? If it's a domain name, you should be able to get a certificate pretty easily.

@endervad
Copy link
Author

It's a domain name, but not mine, it's ISP's. This same domain name is also used by ISP on their website, e.g. their website is example.com and DNS name to my assigned public IP is hostXXXX.example.com. Hence there's no way they would give me the private key to their certificate.

@jmshrv
Copy link
Owner

jmshrv commented Aug 18, 2021

You may be able to get a certificate for just your subdomain: https://stackoverflow.com/questions/39322112/multiple-subdomains-with-lets-encrypt

@endervad
Copy link
Author

endervad commented Aug 18, 2021

That would require action from ISP side, since they're the owner of the whole domain with all subdomains.
To get a certificate for a domain and/or subdomain you need to pass check(s) and prove that you're this domain owner. I'm not a domain owner in this case, I was just lucky enough to have a domain name for my IP. Ideally ISP could get a certificate specifically for my subdomain, yes, but there's no way they're going to do that, they're not that cooperative.

@jmshrv
Copy link
Owner

jmshrv commented Aug 18, 2021

Let's Encrypt's "most common" challenge type just requests a web path on your server with the certificate, no DNS required. I doubt it would work if your ISP has some kind of wildcard certificate for all domains though.

https://letsencrypt.org/docs/challenge-types/

@endervad
Copy link
Author

Okay, somehow I was sure that DNS-01 is a "must" initially, and only then either DNS-01, or HTTP-01 is used for automatic renewing. My bad :D
I was able to get a proper certificate, it's all good now on release version of Finamp. Sorry for taking up your time and thanks for pointing the challenge type stuff out :)
I can still see a scenario though, where allowing self-signed cert could be necessary. For example, if a person has public IP, doesn't have a domain name, and still wants to secure his traffic while not paying for a domain. It's possible to issue a certificate to an FQDN and then specify a desired IP in SAN extension. https://1.1.1.1 did it this way.

@Trinnik
Copy link

Trinnik commented Oct 21, 2021

I have run into this problem with my setup at home and here's my scenario.

None of my services are publicly available, instead, I have an SDN setup using ZeroTier with an nginx proxy server that serves certificates locally. I have my root certificates installed on my user's devices so that they are able to have remote access including https with no open ports. This allows me the ability for my users to access everything as though it were on the public internet without having it actually available to the internet for security purposes.

Would there be a way to have this app use the user certificate stores provided by the OS?

@jmshrv
Copy link
Owner

jmshrv commented Oct 21, 2021

Im assuming ZeroTier works by you connecting to an IP? If you control a domain, you could use the DNS-01 challenge. If not, it looks like ZeroTier's network is encrypted, however data may not be encrypted when travelling over the LAN.

I'll have a look at allowing custom certificates since I'm sure there are people out there who can't use a regular certificate, but last time I looked the only stuff I could find was just saying to disable certificate error handling, which isn't exactly secure.

@Trinnik
Copy link

Trinnik commented Oct 21, 2021

ZeroTier uses peer to peer with root servers to negotiate the connection so unfortunately, everything on the network operates with virtual lan addresses' and node IDs, not publicly routeable.

I have no idea how you get access to the certificates trusted by the operating system but both Android and IOS allow you to trust foreign certificates. That's how I get my browsers to still tell me the webpage is secure. As long as my root certificate is installed at the OS level, it shows as a trusted cert.

I would assume that there's a way to access the trusted certificates from the OS. Love the app otherwise!

@jmshrv
Copy link
Owner

jmshrv commented Oct 22, 2021

I'll have a look into it then, or I'll at least add an option to disable certificate checking.

@sanyarnd
Copy link

Same thing here.

  • I have a free DNS name from Mikrotik
  • my ISP blocks 80 port

=> can't use HTTP/DNS challenge and have to use self-signed certificated :(

Would be great if application will promt about self-signed certs, and make user accept it, like browser do.

@skid9000
Copy link

I'll have a look into it then, or I'll at least add an option to disable certificate checking.

Hello, i'm bumping this issue cause i use a local CA to sign my certificates but it seems that finamp dosen't use Android local user CA base to check if it's trusted or not.

I do want to still use my own CA so without disabling certificate checking, could that be possible ?

Thx !

@jmshrv
Copy link
Owner

jmshrv commented Feb 11, 2022

I believe this is an issue with Dart (or maybe BoringSSL?). I'll have a look into it because it's an annoying issue but no promises since poking around a programming language's source code isn't the easiest thing in the world.

@zuoez02
Copy link

zuoez02 commented Jun 7, 2022

Why not just add a checkbox on login screen? Just let user choose trust the server or not?

@ryneeverett
Copy link

Along the lines of the previous suggestion, I'd propose as an alternative to using the system's CAs, apps like this should allow permanent exceptions the way a browser does when you click through all the warnings. It's basically a TOFU model because it only adds an exception for that specific certificate. If you change the certificate you're back to the warning page, or in the case of this app you'd be back to the login page.

@avanier
Copy link

avanier commented Sep 22, 2022

From what I can see even system certificates are not trusted. (And I'm aware that this barely makes sense as a statement.) I'm trying to connect to a Jellyfin instance that has a known good self-signed certificate for which I have the root CA for the signing authority in my system bundle and I'm getting the handshake error. Is this the expected behaviour?

From the Flutter documentation it seems like CAs that are part of the system bundle should be respected. 😕

Edit: After a bit of searching it seems this thread describes the situation quite well ... which is that even if I speak fluent x.509 I barely understand the Dart developers' explanation. 😝

@avanier
Copy link

avanier commented Sep 22, 2022

Alright, uh, so mostly bad news. I've been digging down this 🐰 🕳️ and Flutter simply does not support using the system bundles at all since Chopper does not respect network-security-config. It's a known bug. 😞

I tried building the app while injecting my root CA as an additional anchor ... and even that's not being respected. You can find sample code (that cannot work) under this commit avanier@229f038 . You'll see on the same branch as a drive-by I also tried updating Chopper to 5.0.0 and that didn't help.

I think the only realistic solution may be to add a checkbox to ignore the error like @zuoez02 suggests. Otherwise, to properly allow the use of system bundles would require getting rid of Chopper for something that makes native calls with the system API. Not a small task, and I'm not even sure it's possible at all under Flutter. (Lowly sysadmin here, I know absolutely nothing of Android development.)

@jmshrv
Copy link
Owner

jmshrv commented Sep 22, 2022

Yeah...

It's a really weird omission from Dart, such a shame that we can't support it

@OdinVex
Copy link

OdinVex commented Jan 31, 2023

I wonder if Chopper can simply be updated to respect system certs or if 'per cert hash' CAs can be allow-listed. Most solutions floating on Flutter's Issues list all revolve around modifying the Trust Anchor and such, which is fine by me (on a per-cert case similar to Mozilla Firefox's old methods before they went fully moronic against non-$$$ certs). ...I notice Flutter has over 5K Issues...wow.

@jmshrv
Copy link
Owner

jmshrv commented Jan 31, 2023

I'll have to have a look to see how disabling certs worked, iirc you got a callback when something went wrong, so you might be able to override it for a specific cert.

Also, this isn't Chopper's fault, it's an issue in Dart. Otherwise, this would be a simple "move to a library that does support custom certificates"

@OdinVex
Copy link

OdinVex commented Jan 31, 2023

I'll have to have a look to see how disabling certs worked, iirc you got a callback when something went wrong, so you might be able to override it for a specific cert.

Also, this isn't Chopper's fault, it's an issue in Dart. Otherwise, this would be a simple "move to a library that does support custom certificates"

If I recall, any custom callback circumvention has issues with Google Playstore certification/qualification? I believe I read that somewhere within Flutter's 5K Issues. I think for now it'd be best to just add a checkbox to ignore issues, but perhaps alert the user when the thumbprint of the cert has changed, if so. A decent-ish middle-ground until devs learn to stop using bad SSL/TLS backends that don't respect system-store certs. This problem is prevalent across far too many softwares for me to maintain. Epidemic.

@jmshrv
Copy link
Owner

jmshrv commented Mar 16, 2023

Some relevant issues:

dart-lang/sdk#48056
dart-lang/sdk#50435

This function also looks interesting, we could have a manual option for now: https://api.dart.dev/stable/2.19.4/dart-io/SecurityContext/setTrustedCertificates.html

@OdinVex
Copy link

OdinVex commented Apr 2, 2023

This doesn't seem to be getting any fixes, despite the situation being rather small to handle. I'd think this kind of issue would be a much higher priority than theming and coloring, eh. How about releasing two builds, then, one with the worst option of allowing all certs and one without? (Or just provide a bool-toggled checkbox...)

@jmshrv
Copy link
Owner

jmshrv commented Apr 2, 2023

I'll get it sorted for the next release, which should hopefully be out this month. I've been busy with uni work since the last release, so I haven't been able to contribute much (a similar thing happened last year)

@OdinVex
Copy link

OdinVex commented Apr 2, 2023

I'll get it sorted for the next release, which should hopefully be out this month. I've been busy with uni work since the last release, so I haven't been able to contribute much (a similar thing happened last year)

University should definitely come first then, totally understand. Awesome. :) I think the “safest” option for this would be to present a dialog to ask the user if they'd like to store the thumbprint of certs in a string array (of thumbprints) when attempting any connection and do thumbprint validation in the HttpOverrides codeblock (describing the allow-bad-certs branch modifications) and allow managing the list in the Settings. It lets everyone enjoy their cake and shouldn't be difficult to manage.

@jmshrv
Copy link
Owner

jmshrv commented Apr 3, 2023

If setTrustedCertificates does what it sounds like, we could probably do it "properly" and have the certificate still be checked. It looks like the Flutter/Dart team are talking this seriously based on the activity in the issues linked here, so it'll hopefully be even more proper soon :)

@OdinVex
Copy link

OdinVex commented Oct 21, 2023

This issue is 2 years old by now. Can we please just get a checkbox that trusts this certificate?

As stated in this Issue: Google won't allow developers to include options like that if they're going to use the Play Store. For versions through F-Droid, entirely possible. To allow Finamp to use the system store (certificate store) the backend library (not controlled by Finamp developer(s)) needs to implement and respect proper design. Edit: It would definitely be nice to at least get the option for F-Droid builds.

@PlexSheep
Copy link

PlexSheep commented Oct 21, 2023

Yes. I understand it's a restriction with Google play. I'm personally using FDroid and I just can't use finamp when my servers certificate is not accepted.

I don't know how much of a maintenance effort it would be to do two versions, but it would be very handy. Especially regarding the fact that the used library/environment does not seem to address the issue.

Edit: I believe that the impossibility to use self signed certificates should be mentioned in the Known Issues section to avoid confusion.

@OdinVex
Copy link

OdinVex commented Oct 21, 2023

Yes. I understand it's a restriction with Google play. I'm personally using FDroid and I just can't use finamp when my servers certificate is not accepted.

I don't know how much of a maintenance effort it would be to do two versions, but it would be very handy. Especially regarding the fact that the used library/environment does not seem to address the issue.

It'd be rather simple and easy, just a pain to make sure it is F-Droid's compilation. If you're familiar with C/C++, think #define and note that being able to define variables for your project on a repo (GitHub or through F-Droid compile process) would allow you to #ifdef the scenario to include code specifically for F-Droid builds that would add a checkbox and the functionality. No maintenance afterward, just the initial setup and to remember about this until the library backend gets theirs together proper.

Edit: I believe that the impossibility to use self signed certificates should be mentioned in the Known Issues section to avoid confusion.

Agreed, regardless of whether F-Droid has enabled builds or not. It should also note that Jellyfin's app can do music, too, just not as designed for music player-like interaction. I've been using it instead, despite limitations.

@Chaphasilor
Copy link
Collaborator

Just to recap:

  • Trusting custom certificates seems to be not possible. There are issues with the Flutter framework itself, and also with the HTTP client we're using
  • Trusting all certificates might be possible, but I wouldn't bet on it. The native code we're using comes from other libraries, not us, and if they don't support it we're probably out of luck. I'm not sure how far @jmshrv got with his investigations...

I agree that adding a checkbox that explicitly disables all checks. This may or may not be supported by Google Play, but a custom build for download from GitHub should be entirely possible, once he issues above are solved.

@jmshrv
Copy link
Owner

jmshrv commented Oct 21, 2023

The native code may use the system stores as intended, I haven't got around to this as I'm selfishly not affected by it myself

@PlexSheep
Copy link

The native code may use the system stores as intended, I haven't got around to this as I'm selfishly not affected by it myself

Can you clarify what you mean by may?

@jmshrv
Copy link
Owner

jmshrv commented Oct 21, 2023

I haven't actually checked but I'd assume ExoPlayer/AVPlayer would respect system certs as other native apps do

@OdinVex
Copy link

OdinVex commented Oct 21, 2023

Just to recap:

* Trusting custom certificates seems to be not possible. There are issues with the Flutter framework itself, and also with the HTTP client we're using

You can add certs to be trusted, it would work, you'd just need to implement, but you'd run into the same Google PlayStore issue. They don't allow that.

* Trusting _all_ certificates might be possible, but I wouldn't bet on it. The native code we're using comes from other libraries, not us, and if they don't support it we're probably out of luck. I'm not sure how far @jmshrv got with his investigations...

Same issue, Google PlayStore. Edit: If the native backend brings support then it'd work and you wouldn't need to do anything at all.

I agree that adding a checkbox that explicitly disables all checks. This may or may not be supported by Google Play, but a custom build for download from GitHub should be entirely possible, once he issues above are solved.

GitHub, especially F-Droid, that'd be awesome.

@jmshrv
Copy link
Owner

jmshrv commented Oct 22, 2023

I can't find anything from Google saying that ignoring certificate errors isn't allowed. Browsers let you do it, after all

@OdinVex
Copy link

OdinVex commented Oct 22, 2023

I can't find anything from Google saying that ignoring certificate errors isn't allowed. Browsers let you do it, after all

I can't recall where I saw it (from Google) but it stated that apps that attempt to modify (specifically to reduce) security by providing a work-around like this would be violating the Play Store's policy because it puts the end user "at risk." It has to do with the API used to verifying certificates, that's what they check (edit: as I was informed at the time I ran into this issue a long while back with other apks, unrelated to signing). If it uses a custom function to bypass validation (what this does) then it's not allowed. badCertificateCallback if I recall (edit: for this situation, I mean, that's the API used for Dart and such).

https://stackoverflow.com/questions/38295073/google-play-developer-console-rejected-my-application-update

This wasn't where I saw the issue at all, it was from Google's own development docs. I can't find that info from memory, I do little Android development. A quick search popped that up demonstrating.

Relevant Snippet:
...application has an unsafe implementation of the WebViewClient.onReceivedSslError handler. Specifically, the implementation ignores all SSL certificate validation errors, making your app vulnerable to man-in-the-middle attacks. An attacker could change the affected WebView's content, read transmitted data (such as login credentials), and execute code inside the app using JavaScript. ... Apps with vulnerabilities that expose users to risk of compromise may be considered in violation of our Malicious Behavior policy and section 4.4 of the Developer Distribution Agreement. Please ensure all apps published are compliant with the Developer Distribution Agreement and Developer Program Policies. If you have questions or concerns, please contact our support team through the Google Play Developer Help Center.

You must present the error every time, though. If you want to get around that you need to add the CA as a cert (store it, append it, give the user a way to manage that, required) and load it on startup every time, or wait until the backend trusts the system store instead of loading its own certs or whatever screwy thing they're doing.

@Chaphasilor
Copy link
Collaborator

I'd argue a checkbox that's not checked by default and maybe hidden in an "advanced settings" menu is not a "vulnerability that exposes users to risk", it's a compatibility option.
In any case, if it's not too much work to implement that handler, we should just try and see what Google says...

@OdinVex
Copy link

OdinVex commented Oct 22, 2023

I'd argue a checkbox that's not checked by default and maybe hidden in an "advanced settings" menu is not a "vulnerability that exposes users to risk", it's a compatibility option. In any case, if it's not too much work to implement that handler, we should just try and see what Google says...

That's literally all you have to do, make it a non-default option. Just overriding to accept all without informing the user and allowing them to reject/prevent is what Google won't allow. Having it as an option that is off by default is what they want. I felt the easiest way to implement the fix regardless of the backend's support was to quite literally attempt connection and if ERR present the cert chain[0] (will be root of cert-chain if using custom CA or will be self-signed, etc) info to allow user to connect or to connect and add the thumbprint to an allowlist (to allow 'do not show again for this server' functionality, can compare the thumbprint and allow). Allow managing that list, rather simple. I'd write the code myself if Android Studio didn't suck so much as an IDE. Better would be to store the cert itself and add as trusted CA. For cert chains, present each cert.

@Chaphasilor
Copy link
Collaborator

@OdinVex feel free to write the code, no need to use Android Studio. VS Code is all you need. But I don't think the allowlist and thumbprint detection is as easy as it seems. This is a cross-platform framework, almost nothing is consistent or easy to access when it comes to low-level functionality...

@OdinVex
Copy link

OdinVex commented Oct 22, 2023

@OdinVex feel free to write the code, no need to use Android Studio. VS Code is all you need. But I don't think the allowlist and thumbprint detection is as easy as it seems. This is a cross-platform framework, almost nothing is consistent or easy to access when it comes to low-level functionality...

The code took 15 minutes to write. It's the <screaming rampage> UI <new curse words> stuff that's in my way. I've already implemented it in Finamp's Settings, the callback to detect if it's been approved before,, etc. Presents simple dialogs to confirm connection, warn if the cert changed, stores per host and per port and per cert thumbprint. Edit: As for VS Code, I don't touch MicroJunk's stuff.

@Chaphasilor
Copy link
Collaborator

@OdinVex if you already have mostly working code, why not open a draft PR so we can take a look and get it ready to merge? :)

@OdinVex
Copy link

OdinVex commented Oct 22, 2023

@OdinVex if you already have mostly working code, why not open a draft PR so we can take a look and get it ready to merge? :)

The UI to remove/reset the list also needs implementing. I might indeed let someone else handle that, I've metaphorically lost my mind hate-thinking at Android, Java, Dart, Flutter... I prefer Assembly/C/C++. At the moment I'm running some test code to make sure it behaves map-wise the way it should. I suspectputIfAbsent doesn't do as I had hoped, so I may need to tamper that logic shortly.

@Chaphasilor
Copy link
Collaborator

I'd be happy to help out with UI issues. Looking forward to the PR!

@OdinVex
Copy link

OdinVex commented Oct 22, 2023

I'd be happy to help out with UI issues. Looking forward to the PR!

Here is the initial Pull Request. See the notes. Edit: For everyone unfamiliar with coding for Android following the thread: This is just back-end work, I don't use Dart/Flutter front-ends, nor did I provide a way to access a list of certificates overridden. That should come easily to a front-end developer and you could theoretically have a final working solution today, depending on how much effort can be put into it. A simple menu in configuration/settings after login to edit overrides, a simple dialog with proper contextual messages/warnings on connect, and automatic "connection reattempt after decision" is all this needs to be a final working solution.

@nrdxp
Copy link

nrdxp commented Dec 25, 2023

I use step-ca to provide an internal cert authority for services on my private (nebula based) mesh network. I install the root certificate to all the devices in the network, including my phone, to allow for apps like jellyfin to use TLS connections without issue. This works on all apps except finamp. It works on the official jellyfin app, for example.

If I understand this thread correctly, this is because the Dart language does not have an interface to check against user installed certificates on android? Is there an open issue for this upstream? I'd be happy to ping that thread as well.

@OdinVex
Copy link

OdinVex commented Dec 25, 2023

I use step-ca to provide an internal cert authority for services on my private (nebula based) mesh network. I install the root certificate to all the devices in the network, including my phone, to allow for apps like jellyfin to use TLS connections without issue. This works on all apps except finamp. It works on the official jellyfin app, for example.

If I understand this thread correctly, this is because the Dart language does not have an interface to check against user installed certificates on android? Is there an open issue for this upstream? I'd be happy to ping that thread as well.

Check out (and build) the certificate-overrides branch. You can try it.

@Chaphasilor Chaphasilor changed the title Failed to connect to server - HandshakeException Self-signed certificate support Jan 28, 2024
@spacekitteh
Copy link

Is any progress being made on this, especially for the FDroid version?

@OdinVex
Copy link

OdinVex commented Sep 5, 2024

Is any progress being made on this, especially for the FDroid version?

Unfortunately not by me, I auto-patch bundled software and root my devices, I'm also porting phones to Linux (think Postmarket OS) to dump Android, so I'm out. :/

@Chaphasilor
Copy link
Collaborator

The Flutter app for Immich has recently added support for this, so we should be able to follow their approach. I've left a comment about it in #546. I don't use self-signed certs myself, so I'd appreciate someone with more experience in that regard to give it a go.

@OdinVex Finamp also runs on Linux now and I've heared of people using it on PostmarketOS/ARM. Just thought I'd let you know, in case you want to keep using it ^^

@OdinVex
Copy link

OdinVex commented Sep 5, 2024

The Flutter app for Immich has recently added support for this, so we should be able to follow their approach. I've left a comment about it in #546. I don't use self-signed certs myself, so I'd appreciate someone with more experience in that regard to give it a go.

I looked into how they did it. It's the "will get your app banned from the Play Store if Google finds out" way. Google is strict about that API and specifically warns (threatens, actually) developers from doing that.

@Chaphasilor
Copy link
Collaborator

I guess we'll just wait a bit and see what happens to Immich then ^^
If they don't get removed, as a much more popular app than Finamp and a direct competitor to Google Photos, than it might be worth the risk :)

@OdinVex
Copy link

OdinVex commented Sep 5, 2024

I guess we'll just wait a bit and see what happens to Immich then ^^ If they don't get removed, as a much more popular app than Finamp and a direct competitor to Google Photos, than it might be worth the risk :)

Originally Google would scan binaries to see if they attempted to override/hook/utilize that API but it's been a few years. The F-Droid version could get away with it no problem. I always tried to use F-Droid versions anyway when I was on Android.

@Chaphasilor Chaphasilor added the hacktoberfest Issues available for Hacktoberfest label Oct 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest Issues available for Hacktoberfest
Projects
None yet
Development

No branches or pull requests