[ANN] WebAuthn.jl

Hi,

I’m happy to announce WebAuthn.jl, a Julia package for W3C WebAuthn and modern passwordless authentication using passkeys and security keys.

WebAuthn.jl brings end-to-end functions for registration and login, CBOR/COSE key parsing, secure challenge generation, signature verification, and PEM export—making it straightforward to integrate passkey or FIDO2/U2F flows into Julia web servers or applications without any required web framework.

Features

  • CBOR/COSE key parsing (P-256/ES256, RSA/RS256, Ed25519)
  • Base64url encoding/decoding & random challenge generator
  • Registration and authentication option builders (browser-ready)
  • Secure signature and challenge verification (OpenSSL/Sodium-backed)
  • PEM export for downstream tools or libraries
  • Fully framework-agnostic code (works with any HTTP/Socket tool)

How WebAuthn Works

WebAuthn enables passwordless, phishing-resistant login using public-key cryptography.
A user’s device generates and stores a private credential (passkey); the private key never leaves the device or browser.

webauthn_registration
webauthn_authentication

Demo & Workflow

Here are a few screenshots from the included minimal server and client example (HTML/JS assets provided in the source code):

For the full example look here.

You can test WebAuthn with a physical security key, or (in Chrome) via your smartphone over Bluetooth:

  • Insert a hardware device and register.
  • Or, scan a QR code with your phone to use it as a “virtual” security key.



Documentation & Contributions

P.S. This also serves as the foundation for a third-party AuthPlugin for GenieFramework, which I hope to publish in the future.

Please try it out and give feedback!

12 Likes

Are you a human, @andreeco ?

Did you use an LLM in the creation of this package?

If you are indeed a human, could you talk with us a little about who you are, how you created this package, and about the thought processes that led you to believe that this is a good idea?


For other people seeing this conversation, take a look at the linked github. Look at WebAuthn.jl/src/signature.jl at 87bce0746901b07ebb1a6b361c0d165c6c150d8d · andreeco/WebAuthn.jl · GitHub and the code for “parsing” ans1/der WebAuthn.jl/src/cose_keys.jl at 87bce0746901b07ebb1a6b361c0d165c6c150d8d · andreeco/WebAuthn.jl · GitHub


If you concur, @mbauman , I’d ask you to not completely delete this thread outright, but rather rename and move to Meta Discussion – I think there is an important conversation to be had; either with @andreeco if he turns out to be a well-intentioned human, or within ourselves if this turns out to be spam / slop / a supply chain attack.

Hi foobar_lv2,

I will explain a bit and you will see I’m human. Recently I had to switch from MbedTLS to OpenSSL since I read that MbedTLS had security issues and it’s maybe discontinued. (See the Slack channel for the Binary Builder and my posts there.) When you look here you will understand line 65 also. I primarily had a lot of troubles with verify_webauthn_signature which is why the function in line 105 was originally used. But the Avoid this too? Line 65 is there because I think it’s better to use multiple dispatch with OKPPublicKey, RSAPublicKey… The basic issue might have been that I had troubles with verification because I did not hash properly before verification? The conversions (pem, der) are there because of my attempts to solve the verification properly.

In the Binary Builder Channel you will also see that I faced issues building tarballs for libfido2, which would be also reasonable for WebAuthn.

This was done when I asked an LLM how to break functions into smaller parts so that they are not too long. This is also when asn1_parse_length became its own function.

I did use an LLM for creation of the package in a structured way and I especially tried to meet as much of the specs of WebAuthn as possible as you will see in the tests. But I think that’s good? I found out for example that CBOR.jl does not meet the specs and should error at a certain point.

I currently try to implement a PaymentsPlugin for GenieFramework. For this I first need a secure user management. When I did this I created the packages OneTimePasswords and WebAuthn so other non non-Genie frameworks can also benefit from it.

I will pass some screenshots from what I develop and where this WebAuthn originated from.










To my background I did study economics and learned Julia some years ago after my master thesis in econometrics using R. I wanted to learn Python but before deciding I thought that it would be wise to check first what is the best language for me. I saw that BlackRock uses Julia for Alladin and NASA also. Then I learned Julia by reading the documentation over and over again. I use Julia for economical simulations in my job.

But more important: Is there a issue with the quality of my code in the sense of security?

1 Like

If you have concerns, you may want to leave a blocking comment on the registration at New package: WebAuthn v0.1.0 by JuliaRegistrator · Pull Request #139195 · JuliaRegistries/General · GitHub

There is no rule that LLMs cannot be used for the development for Julia packages, but the submitting author has to take the responsibility for whatever is being submitted. What is definitely not okay is to let an LLM generate a package and then submit it without detailed human review. If you think that is what is going on here, or if you think the package has some other major problem (whether it’s LLM-generated or not), you should block the registration until the issue is resolved.

it’s not obvious to me what is wrong with the code at those links.

1 Like

Apologies for assuming you were AI.

Then we can have a productive conversation about security / code quality instead of a conversation about spam removal, yay!

No offense to you @andreeco – I think you don’t have ambitions to write security-critical code, and you currently should not publish security-critical code for others, and you’re currently not qualified to review security-critical code that has come out of an LLM.

You should wrap an established package. Using openssl / sodium instead of writing your own crypto is a good start, but the remaining complexity – asn1/der parsing, etc – is clearly not what you should do yourself.

The asn1/der “parsing” code does not parse. You should not write your own “parser” for that, just like you do not write your own crypto. Above link searches a binary blob for a 0x03 byte and checks whether that could be the start of a BITSTRING.

This is the same category error as using a regular expression to parse HTML, in an explicitly security-critical package.

The correct answer when encountering that is to take the person aside, immediately ensure that they will pause publishing security-critical code, review/yank all security-critical code they ever touched, and then once all fires are taken out, explain them the issue.

This is not necessarily an issue of knowledge, but rather an issue of people not respecting the limits of their own knowledge and how irresponsible it is to spread wrong security-critical code in the ecosystem.

There is an internet classic for the regex / html thing

I do absolutely agree and that is what I thought I did. All verifications go through either Sodium, OpenSSL (and previous MbedTLS).

My goal was not to write a crypto library.

The issue with find_bitstring, asn1_parse_length … happened because OpenSSL_jll is not build with ossl-modules and MbedTLS seems to be insecure in Julia at the moment. I stopped the register myself until this is solved. Help is wanted!

But I think there is also a place where normal users should be able to look up which algorithms are secure and use them via a proven library.

1 Like

I disagree with the sentiment that one should not attempt to implement their own crypto, even if the person does not have prior experience in doing so. This is how we attract experienced cryptography professionals to the Julia community by growing them ourselves. The only thing that matters is the enthusiasm and willingness to learn as one goes and put that into practice.

Regarding code, testing had been done somewhat systematically. In addition, using OpenSSL_jll and Sodium is a good practice, as it leverages existing cryptographic implementations. The code appears to be primarily focused on piping data in and out for thoose implementations.

However, I agree that the chosen way to implement parsing really sucks. It is not possible in your case to take the parser apart and test it separately with test vectors, it is dependent on what is being parsed. A thorn in the eye for me is the cose_keys.jl file with the find_bitstring function. This is lazy!

To improve things, you should implement a DER parser. This is much easier to do in a memory-safe language like Julia. If I were to do it myself, I would create an isolated module like:


module DER

struct DERTree
# internals for the tree representation
end

function decode(::Vector{UInt8})::DERTree end

function encode(::DERTree)::Vector{UInt8} end

function decode_pem(::String)::DERTree end

function encode_pem(::DERTree)::String end

end

Once you have this module, find the test vectors with which you can test the DER implementation.

Then refactor the rest of your code, which would introduce a new pattern in verification, something like (just as an example):

tree = decode(payload)

if tree[1] == “RSA”
    # some other parsing
elseif tree[1] == “EC2”
    x, y = tree[2]
else
    error(“Can’t parse”)
end

Since you have already figured out how to use OpenSSL_jll and Sodium, you can also implement the parser with LLM, bringing a helping hand. Good luck!

2 Likes

Thanks for your reply. I read some of your code while working on this package!

If I parse it myself I would still call into a language that is not memory-safe for verification.

I’m about to wrap Webauthn-rs, which seems to be WebAuthn Level 3 compliant and security audited.

IMO the signatures.jl file where you interface the OpenSSL_jll and Sodium.jl is not that bad. Writing DER parser is also not that hard in a memory safe language if you don’t need top performance.

If you can get it wrapped, go for it!