{"id":18715846,"url":"https://github.com/davidbuchanan314/kurl","last_synced_at":"2025-04-12T13:13:05.623Z","repository":{"id":245463595,"uuid":"817257735","full_name":"DavidBuchanan314/kurl","owner":"DavidBuchanan314","description":"code-golfed curl-like powered by kernel-mode crypto, with no security","archived":false,"fork":false,"pushed_at":"2024-06-24T20:10:20.000Z","size":274,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T13:13:01.069Z","etag":null,"topics":["bggp","bggp5","code-golf","kernel-crypto-api","ktls","linux-kernel-crypto","linux-kernel-tls"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DavidBuchanan314.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-19T10:35:12.000Z","updated_at":"2024-12-31T05:48:03.000Z","dependencies_parsed_at":"2024-06-24T21:47:38.071Z","dependency_job_id":"57b18eaa-2f92-4e25-b176-5dfb100d6940","html_url":"https://github.com/DavidBuchanan314/kurl","commit_stats":null,"previous_names":["davidbuchanan314/kurl"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidBuchanan314%2Fkurl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidBuchanan314%2Fkurl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidBuchanan314%2Fkurl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidBuchanan314%2Fkurl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavidBuchanan314","download_url":"https://codeload.github.com/DavidBuchanan314/kurl/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248571863,"owners_count":21126522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bggp","bggp5","code-golf","kernel-crypto-api","ktls","linux-kernel-crypto","linux-kernel-tls"],"created_at":"2024-11-07T13:10:26.326Z","updated_at":"2025-04-12T13:13:05.602Z","avatar_url":"https://github.com/DavidBuchanan314.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kurl\n\nThis is repo hosts my WIP entry to [BGGP5](https://binary.golf/5/). This README acts as a dev log of sorts (It's a bit of an un-edited stream of consciousness right now, I'll do a proper writeup later. hopefully).\n\nThe main goal of BGGP5 is to download the file at [https://binary.golf/5/5](https://binary.golf/5/5) and display its contents, using less than 4KB of code (stored in whatever format you like).\n\n*My* goal is to create the smallest possible HTTPS client as a static Linux ELF binary. I don't yet know if that's possible, within the 4KB constraint! I'll definitely have to cut corners to make it work, especially in the security department.\n\nTiny disclaimer: As part of the BGGP staff team I knew about the theme in advance, and I absolutely could not resist getting started a few days early. This entry is more about being cool than being competitive, so I hope you can forgive me!\n\n## The Plan\n\n\"HTTPS\", for my purposes, means HTTP 1.1 over TLS 1.3 (over TCP, over IP, over...)\n\nThe HTTP part is easy, it's a super simple protocol. At least, the part for requesting a document is simple:\n\n```sh\necho -en $'GET / HTTP/1.1\\r\\nHost: example.com\\r\\nConnection: close\\r\\n\\r\\n' \\\n | nc example.com 80\n```\n\nBut to make it HTTP**S**, we need to add TLS (Transport Layer Security) between the HTTP layer and the TCP layer. Any sane person would want to use a library that implements TLS, like OpenSSL. Or indeed a library that implements HTTPS as a whole, like libcurl.\n\nWhile libcurl and OpenSSL are both way larger than 4KB, with the wonders of Dynamic Linking you don't need to directly include them in your program. But that would introduce a dependency, and I don't like those. I want a strong independent program! To make things both interesting and feasible, I set myself the following constraint:\n\n\u003e I want a self-contained program with no user-space dependencies.\n\nNo *user-space* dependencies means no external libraries, but it does mean we can lean on Linux kernel APIs to help us out. We're already depending on the kernel to load our program and do I/O, so there's no reason not to use it for more. But what more does it have to offer?\n\n[Kernel TLS](https://www.kernel.org/doc/html/latest/networking/tls.html) and the [Kernel crypto interface](https://www.kernel.org/doc/html/latest/crypto/userspace-if.html)!\n\nThese APIs *should* offer everything we need to implement HTTPS \"from scratch\".\n\nIn case it wasn't already obvious, the code I write here isn't going to be \"secure\" by any stretch of the imagination. My goal is simply to retrieve a URL, I don't care about confidentiality or integrity, and I will cut as many corners as I can. I'm only using HTTPS because it's 2024 and nobody serves HTTP anymore.\n\n## Initial Prototyping\n\nI'd never used the ktls or kernel crypto APIs before, and they're not terribly well documented. Rather than diving in with a code-golfed C implementation, I started off with a \"simple\" proof-of-concept in Python, which you can see in `python_prototype/`. The goal here was to understand the APIs, figure out the main \"business logic\" requirements, and provide test/reference values for the inevitable debugging of the final version of the program.\n\n### Kernel TLS\n\nKernel TLS is a very promising idea, on the surface it sounds like it solves all our problems at once. But, there's a sizeable caveat. To oversimplify, TLS has two phases: the handshake (which negotiates parameters and uses asymmetric crypto to establish symmetric session keys), and then the rest of the session that transports the encrypted application-layer traffic. The kernel only assists us with the second half, so we need to do the handshake phase all by ourselves.\n\nAfter many hours of hacking away, I got my prototype to work. I used Scapy to handle the parsing and serialisation of TLS records, python libraries for the handshake crypto, and finally the kernel TLS apis to encrypt/decrypt the session traffic.\n\n[This page](https://tls13.xargs.org/) was absolutely invaluable for understanding the TLS1.3 protocol, along with [RFC8446](https://datatracker.ietf.org/doc/html/rfc8446).\n\nAt this point I realised that 95% of the work *is* the handshake. And once you have the code for the handshake, the amount of additional code required to handle session traffic is miniscule. The ktls API isn't helping much, if at all!\n\nHowever, the other kernel crypto APIs (which I'll investigate further in my C implementation) will at least save us from implementing the crypto primitives from scratch.\n\nFor reference, my python *source* code currently weighs in at about 10KB (including debug logging, comments, whitespace etc.). This is a bit of an apples-to-oranges comparison, but it does give some appreciation for how small 4KB is, while simultaneously giving me hope that I'll be able to golf down my final version to \u003c4KB.\n\n## C Prototype\n\nThe goal for this prototype is to concretely implement everything in C, including understanding the other non-ktls kernel crypto APIs, and implementing TLS record parsing logic myself (replacing scapy).\n\nThis will act as a more readable and debuggable program, acting as a stepping stone to the final golfed version.\n\n### secp256r1 woes\n\nIt was at this point that I realised that the crypto UAPIs don't currently support secp256r1 or any other KPPs (\"Key-agreement Protocol Primitives\"). I wonder... if I set my private key to the scalar value \"1\", then deriving the shared secret from the server's DH share should be trivial (it's just the X coordinate). Will it work?\n\nTurns out, yes it does!!! This obviously completely breaks the security of the protocol, but it means our implementation of ECDH can effectively become a nop. I was worried that maybe servers would try to prevent this in the name of security, but apparently not (there isn't much you can do if the client is being uncooperative).\n\n### DNS\n\nIt just occurred to me that I'm also going to be responsible for DNS resolution. I could hardcode an IP, but that would be a bit lame. I think I'll write my own basic DNS client. BUT, I'll start by hardcoding the IP.\n\n### Compression\n\nAs an aside, it would be nice to be able to utilise compression in some way. Since we have no userspace dependencies, we could in theory pack a custom linux initramfs (a compressed cpio archive) with our executable as the init binary. The main caveat would be getting the kernel to set up its own networking stack properly, which I *think* it can do, with the right kernel commandline options.\n\n## Golfing\n\nRight now, the executable weighs in at 200KB when compiled on aarch64, and 17KB on x86-64.\n\nThe aarch64 build is so large because the sections are padded out to 64k boundaries, compared to 4k on x86. But even if I gzip it (which I use as a very lazy proxy for ignoring all the padding), it's still 6.5KB. Clearly, there's a lot of golfing left to do. A lot of the cruft is going to be coming from dynamicly linking with glibc. Let's remove the glibc dependency, compile to a static ELF, and do bare syscalls with [LSS](https://chromium.googlesource.com/linux-syscall-support).\n\nWith this technique, a \"hello world\" binary weighs in at 512 bytes:\n\n```\n$ gcc kurl.c -o kurl -ffreestanding -nostdlib -static -Os -N -s -fcf-protection=none -Wl,--build-id=none -fomit-frame-pointer -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-ident -fno-stack-protector\n$ hexdump -vC kurl\n00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|\n00000010  02 00 b7 00 01 00 00 00  b0 00 40 00 00 00 00 00  |..........@.....|\n00000020  40 00 00 00 00 00 00 00  00 01 00 00 00 00 00 00  |@...............|\n00000030  00 00 00 00 40 00 38 00  02 00 40 00 04 00 03 00  |....@.8...@.....|\n00000040  01 00 00 00 07 00 00 00  b0 00 00 00 00 00 00 00  |................|\n00000050  b0 00 40 00 00 00 00 00  b0 00 40 00 00 00 00 00  |..@.......@.....|\n00000060  37 00 00 00 00 00 00 00  37 00 00 00 00 00 00 00  |7.......7.......|\n00000070  04 00 00 00 00 00 00 00  51 e5 74 64 06 00 00 00  |........Q.td....|\n00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n000000a0  00 00 00 00 00 00 00 00  10 00 00 00 00 00 00 00  |................|\n000000b0  01 00 00 90 20 00 80 d2  21 60 03 91 c2 01 80 d2  |.... ...!`......|\n000000c0  08 08 80 d2 01 00 00 d4  00 00 80 d2 a8 0b 80 d2  |................|\n000000d0  01 00 00 d4 c0 03 5f d6  48 65 6c 6c 6f 2c 20 77  |......_.Hello, w|\n000000e0  6f 72 6c 64 21 0a 00 00  2e 73 68 73 74 72 74 61  |orld!....shstrta|\n000000f0  62 00 2e 74 65 78 74 00  2e 64 61 74 61 00 00 00  |b..text..data...|\n00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000120  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000140  0b 00 00 00 01 00 00 00  07 00 00 00 00 00 00 00  |................|\n00000150  b0 00 40 00 00 00 00 00  b0 00 00 00 00 00 00 00  |..@.............|\n00000160  28 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |(...............|\n00000170  04 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000180  11 00 00 00 01 00 00 00  03 00 00 00 00 00 00 00  |................|\n00000190  d8 00 40 00 00 00 00 00  d8 00 00 00 00 00 00 00  |..@.............|\n000001a0  0f 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n000001b0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n000001c0  01 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00  |................|\n000001d0  00 00 00 00 00 00 00 00  e7 00 00 00 00 00 00 00  |................|\n000001e0  17 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n000001f0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n00000200\n```\n\nAdding back the rest of the code, porting things as neccessary (memcpy, etc. need reimplementing), results in a 3760 byte binary (compiling for aarch64).\n\nThat's a TLS client in under 4KB!!!!\n\nWe've even got 320 bytes left for a DNS client.\n\nAdding the DNS client brought it up to ~3900 bytes, and then some golfing brought it back down to 3768.\n\nAnd then ~~3744~~ ~~3536~~ ~~3472~~ ~~3432~~ 3232.\n\n## Bonus Features?\n\nNow that I'm comfortably below 4KB, I can think about some enhancements, my dream would be to have something more `curl`-like, which means the ability to pass in a URL. This implies the need for:\n\n- AUXV extraction (normally done by libc before main)\n- basic URL parsing (we can bodge this by passing domain in argv[1] and path in argv[2] but that's a bit unsatisfying)\n- DNS query construction and slightly dynamic response parsing\n- dynamically inserting the hostname and path into the HTTP query\n- dynamically setting the SNI hostname in client_hello (this seems optional for the github pages host, but might not be optional elsewhere)\n- correct recvall logic (not a strict requirement but will increase compat)\n\n## Fixing Fragmentation\n\nWith some sites, (notably google.com), the handshake hangs after a `encrypted_extensions(8)` message. I need to figure out why!\n\nOk it looks like we're not handling record fragmentation properly.\n\nGoogle (and others) are sending some of their handshake responses bundled together within a single outer envelope. We need to iteratively parse records within the decrypted data, I think?\n\n## DNS CNAME\n\nNew problem: DNS responses involving CNAME don't work properly. Example: `old.reddit.com`\n\nSolution: a better response \"parsing\" heuristic.\n\n## ELF Golfing\n\nI was able to save 352 bytes by simply cutting off the end of the file (see `elf_mangler.py`), which included the section headers and strtab (i.e. data not necessary at runtime).\n\nThe next piece of cruft is the `GNU_STACK` program header, which is put there by either gcc or ld (not sure). I can't figure out how to make it not do that. It might be time for a custom linker script?\n\nYup, a custom linker script solved it. I believe there's some more bytes on the table through overlapping the ELF header and program header, and *maybe* putting the entrypoint in the headers too.\n\nIf I write the headers and `_start` in asm, I can have it call `main` and still have the bulk of my code written in C. I implemented this and it works great (see `elf_entry.s`), now we're down to 3071 bytes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidbuchanan314%2Fkurl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidbuchanan314%2Fkurl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidbuchanan314%2Fkurl/lists"}