GithubHelp home page GithubHelp logo

ndilieto / uacme Goto Github PK

View Code? Open in Web Editor NEW
411.0 14.0 36.0 1.15 MB

ACMEv2 client written in plain C with minimal dependencies

License: GNU General Public License v3.0

Makefile 0.93% C 84.35% Shell 8.16% M4 3.30% Roff 3.27%
letsencrypt letsencrypt-cli acme-client acme-protocol acme-v2 uacme tls-certificate ssl-certificate rcf8555 ualpn

uacme's Introduction

uacme manual

uacme

lightweight client for the RFC8555 ACMEv2 protocol, written in plain C with minimal dependencies (libcurl and one of GnuTLS, OpenSSL or mbedTLS). The ACMEv2 protocol allows a Certificate Authority (Let's Encrypt is a popular one) and an applicant to automate the process of verification and certificate issuance. The protocol also provides facilities for other certificate management functions, such as certificate revocation.

Features

  • Lightweight - Unlike most other ACME clients uacme does one thing only and tries to do it well, according to the Unix philosophy. For example don't expect it to automatically set up your webserver to use the certificates it obtains.
  • Written in C - It runs on any unix machine, including Linux, BSD, ...
  • Minimal dependencies - Other than the standard C library, uacme depends only on libcurl and one of GnuTLS, OpenSSL or mbedTLS. It does all the cryptography and network communications without spawning external processes. Particularly when using mbedTLS, it is small enough to run on embedded systems with severe RAM and program memory restrictions (such as OpenWRT routers, for example). This is in contrast to solutions based on python or shell scripts, which may well be a few hundred lines but require many other large applications such as python or openssl to work.
  • Native ECC support - Elliptic Curve keys and certificates can be generated with a commmand line option (-t EC)
  • Easily extensible - It optionally calls an external hook program with the tokens required for domain authorization by the server. The hook program can be an executable, shell script, perl script, python script, or any file that the operating system can execute.
  • ACME challenge agnostic - It provides the user or hook program with all tokens and information required to complete any challenge type but leaves the task of setting up and cleaning up the challenge environment to the user or hook. Example shell scripts to handle http-01, dns-01 and tls-alpn-01 challenges are provided.
  • Zero downtime tls-alpn-01 support - The distribution also includes ualpn, a lightweight proxying tls-alpn-01 challenge responder compliant with RFC8737 and RFC8738.
  • Can run as a cron job - to renew certificates automatically when needed, even for remote machines
  • Robust - It checks every operation, retrying or failing gracefully as appropriate
  • Detailed error reporting - By default totally quiet when everything works ok, it reports precise and detailed error information on stderr when something goes wrong. Optionally it can also print debug information by specifying the --verbose flag once or more.

Installation

Note: pristine releases are in the upstream/latest branch, tagged as upstream/x.x.x

mkdir uacme
wget -O - https://github.com/ndilieto/uacme/archive/upstream/latest.tar.gz | tar zx -C uacme --strip-components=1
cd uacme
./configure --disable-maintainer-mode
make install

If you just want to check out the latest pristine release from github:

git clone -b upstream/latest https://github.com/ndilieto/uacme

uacme is included in several distributions:

Getting started

Once you have obtained uacme (see Installation above) the next step is creating an ACME account:

uacme -v -c /path/to/uacme.d new

The configuration directory and account private key should have been created:

/path/to/uacme.d/private/key.pem

You can then issue a certificate for your domain by doing

uacme -v -c /path/to/uacme.d issue www.your.domain.com

If everything goes well uacme asks you to set up a challenge, for example

uacme: challenge=http-01 ident=www.your.domain.com token=kZjqYgAss_sl4XXDfFq-jeQV1_lqsE76v2BoCGegFk4
key_auth=kZjqYgAss_sl4XXDfFq-jeQV1_lqsE76v2BoCGegFk4.2evcXalKLhAybRuxxE-HkSUihdzQ7ZDAKA9EZYrTXwU

Note the challenge type in the example is http-01 which means you should set up your web server to serve a URL based on the token:

http://www.your.domain.com/.well-known/acme-challenge/kZjqYgAss_sl4XXDfFq-jeQV1_lqsE76v2BoCGegFk4

The URL must return a text file containing a single line with the key authorization:

kZjqYgAss_sl4XXDfFq-jeQV1_lqsE76v2BoCGegFk4.2evcXalKLhAybRuxxE-HkSUihdzQ7ZDAKA9EZYrTXwU

After setting up the web server you can then type 'y' followed by a newline. This notifies the ACME server that it can proceed with the challenge verification. If the procedure is successful uacme saves the certificate and the key at:

/path/to/uacme.d/www.your.domain.com/cert.pem
/path/to/uacme.d/private/www.your.domain.com/key.pem

Note several challenge types are possible. If you type anything other than 'y', uacme skips the challenge and proposes a different one. The easiest is http-01 but any other type can be dealt with. Keep in mind that challenge types may be served in random order by the server. Do not make any assumptions and read what uacme outputs carefully.

Automating updates

Use the -h flag to manage the challenge with a hook script:

uacme -v -c /path/to/uacme.d -h /usr/share/uacme/uacme.sh issue www.your.domain.com

or (depending on your installation)

uacme -v -c /path/to/uacme.d -h /usr/local/share/uacme/uacme.sh issue www.your.domain.com

This will use the example uacme.sh hook script included in the distribution to manage http-01 challenges. You might need to edit the script to match your webserver's environment.

Once everything works correctly you can also set up cron, for example

6 15 * * * /usr/bin/uacme -c /path/to/uacme.d -h /usr/share/uacme/uacme.sh issue www.your.domain.com 

The cron job will automatically update the certificate when needed. Note the absence of -v flag, this makes uacme only produce output upon errors.

Note also that you will need to restart or reload any service that uses the certificate, to make sure it uses the renewed one. This is system and installation dependent. I normally put the necessary instructions in another script (for example /usr/share/uacme/reload.sh) that is executed by cron when uacme returns 0 (indicating the certificate has been reissued).

6 15 * * * /usr/bin/uacme -c /path/to/uacme.d -h /usr/share/uacme/uacme.sh issue www.your.domain.com && /usr/share/uacme/reload.sh

Check https://github.com/jirutka/muacme for a complete, ready-to-go solution.

dns-01 challenge support

The nsupdate.sh hook script included in the distribution allows managing dns-01 challenges with nsupdate. This only works if your name server supports RFC2136 (bind does, nsd doesn't).

https://gitlab.alpinelinux.org/alpine/infra/docker/uacme-nsd-wildcard is another example that works with nsd.

https://gist.github.com/Gowee/e756f925cfcbd5ab32d564ee3c795786 shows how to integrate with Cloudflare API.

https://github.com/tdy91/uacme-gandi-hook works with gandi.net.

https://sr.ht/~jacksonchen666/uacme-desec-hook/ works with deSEC.io.

tls-alpn-01 challenge support

ualpn is a lightweight proxying tls-alpn-01 challenge responder, designed to handle incoming HTTPS connections on port 443. Most of the time it just transparently proxies connections to the real web server (which can be on either another machine, or a different TCP port on the same machine). When a tls-alpn-01 challenge handshake comes in ualpn handles it on the fly instead of proxying it to the webserver. This means that unlike other available tls-alpn-01 responders, ualpn does not require your webserver to stop during the challenge (zero downtime).

The high performance event-driven implementation is based on libev which considerably reduces the cost of context switches and memory usage. In addition on systems such as Linux supporting the splice() system call, ualpn is able to move network data entirely in kernel memory without a round trip to user space, which further enhances performance.

ualpn also listens to a UNIX domain socket so that it can be fed the necessary tls-alpn-01 key authorizations for the domains being validated by the ACME server. ualpn was designed to be easy to integrate with not only uacme (check the example ualpn.sh hook script) but also other ACME clients. A certbot plugin is also available.

To get started with ualpn:

  • move your real HTTPS server to port 4443 which doesn't need to be open to the outside (only ualpn will connect to it) and set it up to accept the PROXY protocol:
  • launch ualpn as a daemon and check the logs (by default in syslog)
    sudo ualpn -v -d -u nobody:nogroup -c 127.0.0.1@4443 -S 666
    
  • create an ACME account
    uacme -v -s -c /path/to/uacme.d -y new
    
  • try obtaining a certificate with tls-alpn-01 challenge
    uacme -v -s -c /path/to/uacme.d -h /usr/share/uacme/ualpn.sh issue www.your.domain.com
    
    or, depending on your installation
    uacme -v -s -c /path/to/uacme.d -h /usr/local/share/uacme/ualpn.sh issue www.your.domain.com
    

Documentation

There are regular unix man pages in the distribution, also available in HTML: uacme ualpn

Bugs and suggestions

If you believe you have found a bug, please log it at https://github.com/ndilieto/uacme/issues

If you have any suggestions for improvements, pull requests are welcome.

uacme's People

Contributors

alexshpilkin avatar dengqf6 avatar ffontaine avatar halosghost avatar jacksonchen666 avatar jirutka avatar kolbma avatar ndilieto avatar sideeffect42 avatar sthen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uacme's Issues

uacme.sh can generate invalid challenge due to echo -n

uacme.sh uses /bin/sh in the shebang which often forces shells such as bash into compatibility mode which interprets echo -n as literal output and thus creates an invalid challenge:

uacme: the server reported the following error:
{
    "type": "urn:ietf:params:acme:error:unauthorized",
    "detail": "The key authorization file from the server did not match this challenge \"tt[...]Wuo\" != \"-n tt[...]Wuo\"",
    "status": 403
}

above example was produced with

$ /bin/sh --version
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin20)

and the echo behavior can be checked with

$ /bin/sh -c 'echo -n'
-n

The specs say that servers SHOULD accept challenges with trailing whitespace so the easiest fix is to remove -n. A more advanced fix would be to first check that -n is safe and not use it if it's not.

Error recognition fails if parameter is present in reply's "Content-Type" header

Hi,

fooling around with uacme and pebble I noticed that account creation always fails. I found that pebble returns the header Content-Type: application/probjem+json; charset=utf-8 in its error replies, which is perfectly valid according to https://tools.ietf.org/html/rfc7231#section-3.1.1.1

Due to using strcasecmp for comparision, this fails on several places, including account creation, which is then wrongly aborted.

I guess the most simple fix would be to just use strncasecmp on those 4 occasions.

Could not find pkg-config

If I am trying to install like described on the main page, I'll get an error after running ./configure --disable-maintainer-mode.

[...]
checking sys/socket.h usability... yes
checking sys/socket.h presence... yes
checking for sys/socket.h... yes
checking for sys/stat.h... (cached) yes
checking for sys/types.h... (cached) yes
checking sys/wait.h usability... yes
checking sys/wait.h presence... yes
checking for sys/wait.h... yes
checking for asprintf... yes
checking for vasprintf... yes
checking for getopt_long... yes
checking for setproctitle... no
checking for pkg-config... no
configure: error: Could not find pkg-config

ZeroSSL keeps returning status:invalid for one out of two authorization request. Expects a retry?

Hey,

Thanks for maintaining uacme! I'm not sure who's issue this is, but I thought I'd start with uacme as you're probably more knowledgeable of the related specs than a random customer service rep from ZeroSSL.

I'm trying to to refresh a certificate with ZeroSSL that has two names --- the root and a wilcard --- example.com,*.example.com. Due to some configuration issue, the authorization was cut short yesterday. That is, only a single auth request succeeded -- for the root example.com. Since then, however, it seems ZeroSSL reuses the authorizations/challenges and considers one out of the two constantly valid and the other invalid.

In other words, the initialization returns two auths:

{
    "status": "pending",
    "expires": "2024-05-04T22:26:35Z",
    "identifiers": [
        {
            "type": "dns",
            "value": "example.com"
        },
        {
            "type": "dns",
            "value": "*.example.com"
        }
    ],
    "authorizations": [
        "https://acme.zerossl.com/v2/DV90/authz/ABC",
        "https://acme.zerossl.com/v2/DV90/authz/XYZ"
    ],
    "finalize": "https://acme.zerossl.com/v2/DV90/order/FOO/finalize"
}

The first constantly returns valid. I presume they remember that yesterday this succeeded:

{
    "identifier": {
        "type": "dns",
        "value": "example.com"
    },
    "status": "valid",
    "expires": "2024-03-05T22:26:35Z",
    "challenges": [
        {
            "type": "dns-01",
            "url": "https://acme.zerossl.com/v2/DV90/chall/XXX",
            "status": "valid",
            "validated": "2024-02-04T22:26:39Z",
            "token": "SNAFU"
        }
    ]
}

The other, the wildcard auth, constantly returns invalid:

{
    "identifier": {
        "type": "dns",
        "value": "example.com"
    },
    "status": "invalid",
    "expires": "2024-03-05T22:26:35Z",
    "challenges": [
        {
            "type": "dns-01",
            "url": "https://acme.zerossl.com/v2/DV90/chall/YYY",
            "status": "invalid",
            "error": {
            },
            "token": "SNAFU"
        }
    ],
    "wildcard": true
}

While I'm not familiar with the ACME protocol, it seems to be ZeroSSL presumes the second challenge should still be used, even though it's currently with an invalid status. Uacme, though, bails out after not seeing pending there and presumes that once the status flips to invalid, it'll never switch to valid.

So, any idea who's bug this is?

Thanks!

Update autotools dependency

It looks like there's currently a hard dependency on aclocal-1.14. On archlinux, for example, the only version of automake available is 1.16, which leads to the following build error:

CDPATH="${ZSH_VERSION+.}:" && cd . && /bin/sh /home/halosghost/prj/pkg/uacme-git/src/uacme/build-aux/missing aclocal-1.14
/home/halosghost/prj/pkg/uacme-git/src/uacme/build-aux/missing: line 81: aclocal-1.14: command not found
WARNING: 'aclocal-1.14' is missing on your system.
         You should only need it if you modified 'acinclude.m4' or
         'configure.ac' or m4 files included by 'configure.ac'.
         The 'aclocal' program is part of the GNU Automake package:
         <http://www.gnu.org/software/automake>
         It also requires GNU Autoconf, GNU m4 and Perl in order to run:
         <http://www.gnu.org/software/autoconf>
         <http://www.gnu.org/software/m4/>
         <http://www.perl.org/>
make: *** [Makefile:385: aclocal.m4] Error 127

I am not hugely familiar with autotools; is this something that is easy to change so that newer versions of automake will continue to build uacme?

Could not create new account on OpenWrt

Hi

This is less of an issue than a request for help because I could barely find any documentation for getting uacme to work on OpenWrt.

I successfully compiled and installed the uacme package for OpenWrt from its package source at https://github.com/openwrt/packages/tree/master/net/uacme

Then I tried to create a new account:
# uacme -v -c /etc/acme/ new
uacme: version 1.2.1 starting on Wed, 28 Oct 2020 06:28:46 -0700
uacme: created directory /etc/acme//private
uacme: loading key from /etc/acme//private/key.pem
uacme: /etc/acme//private/key.pem not found
uacme: generating new 2048-bit RSA key
uacme: key saved to /etc/acme//private/key.pem
uacme: fetching directory at https://acme-v02.api.letsencrypt.org/directory
uacme: creating new account at https://acme-v02.api.letsencrypt.org/acme/new-acct
uacme: type 'y' to accept the terms at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf
y
uacme: account created at https://acme-v02.api.letsencrypt.org/acme/acct/100577813
uacme -v -c /etc/acme/ new

However, open the link https://acme-v02.api.letsencrypt.org/acme/acct/100577813, I got the following error:
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Method not allowed",
"status": 405
}

Could I get any help here?
Thanks!
Wei

Return code from --version should probably be 0

uacme --version 2> /dev/null && echo "OK" || echo "Nope"

This is a common way to determine whether or not the program is present. However, uacme returns a failure code, non-zero, from this command and from uacme --help.

Set CA bundle to verify ACME Server against

I need to issue a certificate from an ACME server whose TLS certificate is not in /etc/ssl/certs/ca-certificates.crt, the default CABundle of Alpine Linux. This results in this error message:

uacme: curl_get: GET https://acme.***:8443/directory failed: SSL peer certificate or SSH remote key was not OK 

I tried setting CURL_CA_BUNDLE, but that didn't work. Is there a way to set which CA Bundle curl_get uses? For reasons I don't want to get into adding the CA to the default bundle isn't desired.

uacme doesn't work with smallstep ca

Hey all,

I have a smallstep certificate authority set up for in-house ACME provisioning, and am running into issues getting uacme to work with it. When running the uacme new command, I get an error saying that the "account does not exist" (see verbose logs below). I am able to create an account and issue a cert using certbot, so I suspect this is either an issue with uacme or a combination of uacme and the smallstep setup.

I really like uacme and would love to get it working with smallstep, so let me know if there's anything else I can do to help debug. Thanks for writing a great tool!

Log:

# uacme -v -v -v -a https://<DOMAIN>/acme/acme/directory new <EMAIL>

uacme: version 1.0.21 starting on Sat, 01 Feb 2020 14:41:55 +0000
uacme: loading key from /etc/ssl/uacme/private/key.pem
uacme: fetching directory at https://<DOMAIN>/acme/acme/directory
uacme: acme_get: url=https://<DOMAIN>/acme/acme/directory
uacme: acme_get: HTTP headers
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: application/json
Replay-Nonce: <NONCE>
Date: Sat, 01 Feb 2020 14:41:55 GMT
Content-Length: 307


uacme: acme_get: HTTP body
{"newNonce":"https://<DOMAIN>/acme/acme/new-nonce","newAccount":"https://<DOMAIN>/acme/acme/new-account","newOrder":"https://<DOMAIN>/acme/acme/new-order","revokeCert":"https://<DOMAIN>/acme/acme/revoke-cert","keyChange":"https://<DOMAIN>/acme/acme/key-change"}

uacme: acme_get: return code 200, json=
{
    "newNonce": "https://<DOMAIN>/acme/acme/new-nonce",
    "newAccount": "https://<DOMAIN>/acme/acme/new-account",
    "newOrder": "https://<DOMAIN>/acme/acme/new-order",
    "revokeCert": "https://<DOMAIN>/acme/acme/revoke-cert",
    "keyChange": "https://<DOMAIN>/acme/acme/key-change"
}
uacme: fetching new nonce at https://<DOMAIN>/acme/acme/new-nonce
uacme: acme_get: url=https://<DOMAIN>/acme/acme/new-nonce
uacme: acme_get: HTTP headers
HTTP/1.1 204 No Content
Cache-Control: no-store
Replay-Nonce: <NONCE>
Date: Sat, 01 Feb 2020 14:41:55 GMT


uacme: acme_get: HTTP body

uacme: acme_get: return code 204
uacme: creating new account at https://<DOMAIN>/acme/acme/new-account
uacme: acme_post: url=https://<DOMAIN>/acme/acme/new-account payload={"onlyReturnExisting":true} protected={"alg":"RS256","nonce":"<NONCE>","url":"https://<DOMAIN>/acme/acme/new-account","jwk":{"kty":"RSA","n":"<REDACTED>","e":"<REDACTED>"}} jws={"protected":"<REDACTED>","payload":"<REDACTED>","signature":"<REDACTED>"}
uacme: acme_post: HTTP headers:
HTTP/1.1 100 Continue

HTTP/1.1 404 Not Found
Cache-Control: no-store
Content-Type: application/problem+json
Link: <https://<DOMAIN>/acme/acme/directory>;rel="index"
Replay-Nonce: <NONCE>
Date: Sat, 01 Feb 2020 14:41:55 GMT
Content-Length: 92


uacme: acme_post: HTTP body:
{"type":"urn:ietf:params:acme:error:accountDoesNotExist","detail":"Account does not exist"}

uacme: acme_post: return code 404, json=
{
    "type": "urn:ietf:params:acme:error:accountDoesNotExist",
    "detail": "Account does not exist"
}
uacme: failed to create account at https://<DOMAIN>/acme/acme/new-account
uacme: the server reported the following error:
{
    "type": "urn:ietf:params:acme:error:accountDoesNotExist",
    "detail": "Account does not exist"
}

segfault in X509_get0_notAfter during certificate issuing

Hey @ndilieto, thanks for uacme. I like it.

I wanted to try out master, and found a segfault arising from a null pointer dereference. I bisected to 1809518 and am building it with the following configuration: ./configure --disable-maintainer-mode --with-openssl --disable-docs

(gdb) run -v issue example.com
Starting program: /usr/local/bin/uacme -v issue example.com
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
uacme: version 1.1.2 starting on Sun, 15 Mar 2020 13:00:19 +0000
uacme: loading key from /etc/ssl/uacme/private/key.pem
uacme: loading key from /etc/ssl/uacme/private/example.com/key.pem
uacme: checking existence and expiration of /etc/ssl/uacme/example.com/cert.pem
uacme: /etc/ssl/uacme/example.com/cert.pem does not exist

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7dc08a0 in X509_get0_notAfter ()
   from /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
(gdb) bt
#0  0x00007ffff7dc08a0 in X509_get0_notAfter ()
   from /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
#1  0x000055555555ff75 in cert_valid (certdir=0x55555559bf70 "/etc/ssl/uacme/example.com",
    names=0x7fffffffeb60, validity=30, status_check=true) at crypto.c:2716
#2  0x000055555555c1bb in main (argc=4, argv=0x7fffffffeb48) at uacme.c:1466
(gdb) break crypto.c:2716
Breakpoint 1 at 0x55555555ff69: file crypto.c, line 2716.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/local/bin/uacme -v issue example.com
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
uacme: version 1.1.2 starting on Sun, 15 Mar 2020 13:01:24 +0000
uacme: loading key from /etc/ssl/uacme/private/key.pem
uacme: loading key from /etc/ssl/uacme/private/example.com/key.pem
uacme: checking existence and expiration of /etc/ssl/uacme/example.com/cert.pem
uacme: /etc/ssl/uacme/example.com/cert.pem does not exist

Breakpoint 1, cert_valid (certdir=0x55555559bf70 "/etc/ssl/uacme/example.com",
    names=0x7fffffffeb60, validity=30, status_check=true) at crypto.c:2716
2716        const ASN1_TIME *tm = X509_get0_notAfter(crt[0]);
(gdb) p crt[0]
$1 = (X509 *) 0x0
(gdb) p ncrt
$2 = 32767

I'm not entirely sure where 32767 is coming from, but I assume the expected behavior is for cert_load to have returned <= 0. Here's a patch for that, which fixed the issue for me:

diff --git a/crypto.c b/crypto.c
index 00488af..71acfa0 100644
--- a/crypto.c
+++ b/crypto.c
@@ -2371,6 +2371,7 @@ static int cert_load(mbedtls_x509_crt **crt, const char *format, ...)
             msg(1, "%s does not exist", certfile);
         else
             warn("cert_load: failed to open %s", certfile);
+        r = 0;
         goto out;
     }
     for (r = 0; r < (int)crt_size; r++) {

Although, you might want to double check that the patch looks good. I see that r's being reused a lot.

Debian package uses libcurl3-gnutls but OpenWrt use libcurl4

I'm confusing about dependencies and would be glad if you can clarify.
On Ubuntu and Debian the uacme package depends on libcurl4.
I didn't found the debian folder with control file but apt shows these dependencies:

Depends: libc6 (>= 2.33)
  Depends: libcurl3-gnutls (>= 7.38)
  Depends: libev4 (>= 1:4.04)
  Depends: libgnutls30

So here I see that the libcurl3-gnutls is used directly by the uacme e.g. not just libcurl3 which may use a different backend by default.
Also the libgnutls30 is used directly by the uacme itself and the package was compiled with it. That's fine.

The question is why not use the libcurl4 dependency? As far I understood there is no any libcurl4-gnutls but it will internally pick up an installed backed library, right?
Update yes, it can be dynamically load an available TPS backend https://curl.se/mail/lib-2017-08/0118.html

The things become more weird for OpenWrt. There the uacme package uses the libcurl4 (not 3!) which itself depends on WolfSSL as it's now the default library for OpenWrt in last releases. But OpenWrt team working on moving back to MbedTLS.

In the same time the uacme itself depends on libmbedtls12. This is understandable but still some OpenWrt based firmwares for routers like Turris and Gl.inet are using just OpenSSL. Maybe for them it would be better to create a separate package like uacme-openssl.

Another strange thing is that the OpenWrt uacme package doesn't depend on libev. In the same time I see that the library is just included into sources. Is it statically linked or something like that?
There is some issue that may be related openwrt/packages#19015

Can't call binary hook.

cat main.cr

`uacme -c /var/lib/uacme/ec -t EC -b 384 -h uacme_hook_knot issue "example.com" "*.example.com"`

cat uacme_hook_knot.cr

pp ARGV

Compile it crystal build ./main.cr
Move binary to server and try to execute

[root@webserver ~]# ./main 
uacme: uacme_hook_knot: No such file or directory

[root@webserver ~]# ls -lha /usr/bin/uacme_hook_knot 
-rwxr-xr-x 1 root root 331K Jul 31 13:43 /usr/bin/uacme_hook_knot

[root@webserver ~]# uacme_hook_knot 1 2 3 4 5
["1", "2", "3", "4", "5"]

But it works on laptop, where i compile it.
With python i got similar issue.
Upd. Now works with -h /usr/bin/uacme_hook_knot, but before it doesn't

Password files

Not sure what the take or philosophy is around this, but have seen other implementations and support for encrypting keys with passwords/password-files, is it something that is within the scope of the project to enable usage of passwords/password-files?

Or perhaps there it is an intentional design-choice.

How to request ocsp-must-staple on certificates

How do I pass ocsp-must-staple flag to uacme?
I grepped the source code but did not find this flag or the option.
I then looked at go-acme/lego specifically this thinking that I would create a pull request but my C language skills are failing me.
What do you suggest?

No joy with install on Raspbian / Buster

Good morning,

I tried installing UACME on my Pi 4 (running Buster). I ran into two installation issues so far when I executed the "./configure --disable-maintainer-mode" command.

  • The first time, it failed due to a lack of libcurl. I installed that and re-ran the installation script.
  • The installer made it past libcurl but then got caught on GNUTLS missing. So I installed that too, via "sudo apt-get install gnutls-bin". The installer script still stalls and errors out, claiming that a suitable GNUTLS has not been installed.

According to the GNUTLS installer, version 3.6.7 was installed "Setting up gnutls-bin (3.6.7-4+deb10u6) ..."
Per the UACME installer, "checking for GnuTLS >= 3.3.30... no" followed by "configure: error: gnutls not found"

I must be doing something wrong so I wonder if you could make a suggestion re: GNUTLS or the version of LIBCURL that should be installed as a pre-requisite to installing UACME. I would also like to make the suggestion to reference these and any other packages that must be installed before UACME to make the process easier for newbies like me. Thank you for writing UACME!

Constantin

support for --reuse-key

Hi, uacme looks very interesting to me but i could not find any information in the docs about the lifecycle of the key, I guess its renewed on renew? If so is it possible to a use or create similar option as certbot uses? ie --reuse-key.

Can't compile uacme on busybox with armv7 hardware because the dependency to libcurl is not resolved correctly

Hello ndilieto,

I am trying to build uacme on my router Netgear R7800
which is running busybox on armv7l hardware.
(500 MB ram, and two cores at 1.7 ghz)

I've installed a gcc compiler , and am running the ./configure script.
the script is reporting a error that libcurl dependency cannot be resolved.

<<
checking for pkg-config... /opt/bin/pkg-config
checking pkg-config is at least version 0.28... yes
checking for libcurl >= 7.38.0... no
configure: error: libcurl not found

I have checked if the libcurl i have is meeting this requirement , and
it seems so. Opkg my package manager is listing libcurl as installed.

root@router:/opt/home/frank/uacme$ opkg list-installed | grep libcurl
libcurl - 7.68.0-1

What can i do to make uacme build?

1.7.5 cross compile failure

with the new update, cross compile doesn't work anymore:

checking for sys/un.h... yes
checking for mmap... yes
checking if mmap(MAP_ANON|MAP_SHARED) works... configure: error: in `/home/build/proxy/build_dir/target-x86_64_musl/uacme-upstream-1.7.5':
configure: error: cannot run test program while cross compiling
See `config.log' for more details

configure:7108: checking for mmap
configure:7108: x86_64-openwrt-linux-musl-gcc -o conftest -Os -pipe -g3 -fno-caller-saves -fno-plt -fhonour-copts -fmacro-prefix-map
=/home/build/proxy/build_dir/target-x86_64_musl/uacme-upstream-1.7.5=uacme-upstream-1.7.5 -Wformat -Werror=format-security -fstack-p
rotector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro  -I/home/build/proxy/staging_dir/toolchain-x86_64_gcc-12.3.0_musl/usr/include -
I/home/build/proxy/staging_dir/toolchain-x86_64_gcc-12.3.0_musl/include -I/home/build/proxy/staging_dir/toolchain-x86_64_gcc-12.3.0_
musl/include/fortify  -I/home/build/proxy/staging_dir/target-x86_64_musl/usr/include -L/home/build/proxy/staging_dir/toolchain-x86_6
4_gcc-12.3.0_musl/usr/lib -L/home/build/proxy/staging_dir/toolchain-x86_64_gcc-12.3.0_musl/lib -fuse-ld=bfd -znow -zrelro -Wl,--gc-s
ections,--as-needed  -L/home/build/proxy/staging_dir/target-x86_64_musl/usr/lib conftest.c -lssl -lcrypto  >&5
configure:7108: $? = 0
configure:7108: result: yes
configure:7119: checking if mmap(MAP_ANON|MAP_SHARED) works
configure:7122: error: in `/home/build/proxy/build_dir/target-x86_64_musl/uacme-upstream-1.7.5':
configure:7124: error: cannot run test program while cross compiling
See `config.log' for more details

Works only on POSIX filesystems: hardlink required

I am using a filesystem which does not support hard links.
So the code like here:

uacme/uacme.c

Lines 1180 to 1186 in 7f1ffbd

if (link(certfile, bakfile) < 0) {
if (errno != ENOENT) {
warn("failed to link %s to %s", bakfile, certfile);
goto out;
}
} else
msg(1, "backed up %s as %s", certfile, bakfile);

to backup the current file fails and the cert.pem.tmp is left at its place, so I've to manually move cert.pem.tmp to cert.pem.

Similar for...

uacme/uacme.c

Lines 745 to 757 in 7f1ffbd

msg(1, "backing up %s as %s", keyfile, bakfile);
if (link(keyfile, bakfile) < 0)
warn("failed to link %s to %s", bakfile, keyfile);
else {
msg(1, "renaming %s to %s", newkeyfile, keyfile);
if (rename(newkeyfile, keyfile) < 0) {
warn("failed to rename %s to %s", newkeyfile, keyfile);
unlink(bakfile);
} else {
msg(1, "account key changed");
success = true;
}
}

Is it doable to copy the file to backup when link fails?

Like with code from here: https://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c

nsupdate.sh may fail silently with exit status to be 0

Hello,

I am adapting the sample hook script for Cloudflare API. I notice a possible problem that may cause the script to fail silently.

At the line

[ $res -eq 0 ] || break
, break is executed if ns_doupdate returns non-zero code. Then it flows to the line
return $?
, where $? is always 0 because previous res=$? (and [ $res -eq 0 ] ?) itself updates $? to be 0.

Finally, the script returns 0 even though ns_doupdate has failed.

A quick fix is to replace break with return $res.

(I do not have a bind server to test the original script. Please let me know if I missed something by mistake.)

chain and fullchain.pem

Hi,

I tried uacme and it gets cert and the key. However, it does not get chain and fullchain as done by certbot.

Can you please add feature or let me know how to do it?

Thanks

run hooks parallel

I would like to have the hooks run parallel. Our DNS provider is quite slow and a change needs up to 20. min. This adds up pro domain.

You haven't considered that having multiple hooks run in parallel will result in very problematic race conditions, for example multiple hooks changing the same webserver or DNS configuration at once.

Actually I have considered this and noticed the scripts currently can run in parallel. Because multiple certs can contain the same domain.

Hooks would need to implement their own locking mechanism to avoid this.

For http and dns challenges this is not a problem, because it's save to assume that the token/auth is different for each challenge.

For tls-alpn ualpn.c might needs some locking, but I would say it's required anyway.

RFC 8738 support / externalAccountBinding problem with ZeroSSL API?

Hi, this is probably two issues in one ;) I need to automate certificate installation for a large network of decentralized clients with static IP addresses but no domain. I came across RFC 8738, I believe uacme is one of the first ACME clients to support this new standard? Let's Encrypt doesn't support it yet (it's in pebble but not boulder), which CA did you test against?

For now I'm trying to use it with ZeroSSL, which supports ACME and IP certificates, but I'm getting the following authentication error (probably unrelated to RFC 8738):

uacme: version 1.5 starting on Sun, 29 Nov 2020 18:11:04 -0800
uacme: loading key from ./private/key.pem
uacme: fetching directory at https://acme.zerossl.com/v2/DV90
uacme: creating new account at https://acme.zerossl.com/v2/DV90/newAccount
uacme: type 'y' to accept the terms at https://secure.trust-provider.com/repository/docs/Legacy/20181101_CertificateSubscriberAgreement_v_2_1_click.html
y
uacme: failed to create account at https://acme.zerossl.com/v2/DV90/newAccount
uacme: the server reported the following error:
{
    "type": "urn:ietf:params:acme:error:externalAccountRequired",
    "status": 400,
    "detail": "The request must include a value for the \"externalAccountBinding\" field"
}

Is it possible to specify an externalAccountBinding with uacme? Thanks for thoughts on these issues!

clang reports warnings

Hello.
Just found your client, and decided that before using it, it would be interesting to see what clang --analyze would say about it.

Turns out that there are several warnings here (I will join the full log and how to generate it, since clang joins the traces it took to find the warnings):

  • null pointer passed as argument to strcasecmp/strdup
  • Potential leak of memory
  • Attempt to free released memory
  • The left operand of '==' is a garbage value
  • Access to field 'tls' results in a dereference of a null pointer

Here is how I generated that:
clang -D SYSCONFDIR='"/etc/"' -D RUNSTATEDIR='"/var/run"' -I./libev --analyze --analyzer-output text *.c > build.log 2>&1

I have also noticed warnings with a different combination of flags:

  • reports expression results being unused in the asserts. Nice trick you have, but could I suggest using assert( condition && "error message" ); instead? It does the same, but I still have to see a compiler whining about it.
  • implicit conversion changes signedness
  • macro name is a reserved identifier
  • format string is not a string literal
  • implicit conversion loses integer precision:
  • enumeration values not explicitly handled in switch (but the default keyword is used at least sometimes, so...)
  • no previous prototype for function
  • declaration shadows a local variable
  • cast from 'struct sockaddr *' to 'struct sockaddr_in *' increases required alignment (can't really be helped though)
  • padding size of 'curldata_t' with 4 bytes to alignment boundary (again, probably can't be helped, and this one only impacts memory impact AFAIK)
  • disabled expansion of recursive macro (I still have to understand the impact of this one)
  • 'HAVE_MAP_DEVZERO' is not defined, evaluates to 0 (this is my fault, since I short-circuited the makefile)
  • cast from 'const void *' to 'unsigned char *' drops const qualifier
  • cast from 'uint8_t *' (aka 'unsigned char *') to 'client_t *' (aka 'struct client *') increases required alignment from (this might be ok, but it also might trigger bugs, and that one could maybe be avoided)
  • cast from function call of type 'long' to non-matching type 'float'
  • implicit conversion increases floating-point precision:

Of course, several of those warnings have only memory performance impact or are false positives, but I believe fixing most of those would allow an easier to maintain code (shadow declarations, names reserved by the standard,...) or less prone to errors (implicit casts, for example).

Those were generated with:
clang -Wall -Weverything -D SYSCONFDIR='"/etc/"' -D RUNSTATEDIR='"/var/run"' -I./libev *.c > build2.log 2>&1
Of course, this generates linking error too, but that's unimportant considering my goal here.

You will find both the analyzer log (build.log) and the clang complains (build2.log) attached.
I intend to check if I can patch some of those quickly.

build.tar.gz

Feature request: print thumbprint of account key

I was thinking this could be useful for people that have configured their web server to statelessly respond to HTTP challenges. This has limitations but it can be done if the thumbprint of the account key is known. Any thoughts on adding a method for uacme to print the thumbprint of the current account key and then exit or similar?

Specify which network interface to use for ACME requests

It would be nice to have an option to specify which network interface or IP address should be used for ACME requests. When working with a large deployment which uses many certificates it can be helpful to work with several IP addresses to manage the LE rate limits.

Security issue in uacme.sh

uacme/uacme.sh

Line 39 in 5afdaf0

printf "%s" "${AUTH}" > "${CHALLENGE_PATH}/${TOKEN}"

The externally controlled TOKEN variable (by the ACME server) is used to construct a path into which an externally controlled value $AUTH is written. This can be exploited by the ACME server to overwrite arbitrary files with arbitrary content.

Compilation failed on ArchLinux.

CDPATH="${ZSH_VERSION+.}:" && cd . && /bin/sh /home/build.dir/uacme/src/uacme/build-aux/missing aclocal-1.14 
/home/build.dir/uacme/src/uacme/build-aux/missing: line 81: aclocal-1.14: command not found

Missing aclocal-1.14, but installed aclocal-1.16

Incompatibility with Mac OS X Monterrey

When I run make install I get this:

config.status: executing depfiles commands
/Library/Developer/CommandLineTools/usr/bin/make  install-am
  CC       ualpn-ualpn.o
ualpn.c:538:10: error: implicit declaration of function 'sem_timedwait' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    rc = sem_timedwait(&g_shm->sem, &ts);
         ^
ualpn.c:538:10: note: did you mean 'sem_trywait'?
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/semaphore.h:58:5: note: 'sem_trywait' declared here
int sem_trywait(sem_t *);
    ^
ualpn.c:3628:13: warning: 'sem_destroy' is deprecated [-Wdeprecated-declarations]
            sem_destroy(&g_shm->logsem);
            ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/semaphore.h:53:26: note: 'sem_destroy' has been explicitly marked deprecated here
int sem_destroy(sem_t *) __deprecated;
                         ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h:204:40: note: expanded from macro '__deprecated'
#define __deprecated    __attribute__((__deprecated__))
                                       ^
ualpn.c:3631:13: warning: 'sem_destroy' is deprecated [-Wdeprecated-declarations]
            sem_destroy(&g_shm->sem);
            ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/semaphore.h:53:26: note: 'sem_destroy' has been explicitly marked deprecated here
int sem_destroy(sem_t *) __deprecated;
                         ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h:204:40: note: expanded from macro '__deprecated'
#define __deprecated    __attribute__((__deprecated__))
                                       ^
ualpn.c:4653:9: warning: 'sem_init' is deprecated [-Wdeprecated-declarations]
    if (sem_init(&g_shm->sem, 1, 1)) {
        ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/semaphore.h:55:42: note: 'sem_init' has been explicitly marked deprecated here
int sem_init(sem_t *, int, unsigned int) __deprecated;
                                         ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h:204:40: note: expanded from macro '__deprecated'
#define __deprecated    __attribute__((__deprecated__))
                                       ^
ualpn.c:4658:9: warning: 'sem_init' is deprecated [-Wdeprecated-declarations]
    if (sem_init(&g_shm->logsem, 1, 1)) {
        ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/semaphore.h:55:42: note: 'sem_init' has been explicitly marked deprecated here
int sem_init(sem_t *, int, unsigned int) __deprecated;
                                         ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h:204:40: note: expanded from macro '__deprecated'
#define __deprecated    __attribute__((__deprecated__))
                                       ^
4 warnings and 1 error generated.
make[1]: *** [ualpn-ualpn.o] Error 1
make: *** [install] Error 2

1.7.1 release tarball is missing configure script

The README instructs to start with running ./configure, but the latest release tarball does not contain one, requiring it to be generated by the user before compiling. Please update the documentation if this is intentional, or bundle a configure script in the source tarball.

Tests

Maybe helpful to add some automated tests of correct functionality, can make pull requests with possible tests that can be used.

uacme exit codes with systemd

uacme's exit codes aren't ideal for use as a systemd oneshot service:

0 Success
1 Certificate not reissued because it is still current
2 Failure (syntax or usage error; configuration error; processing failure; unexpected error).

By default systemd considers a nonzero exit code as a failure:
https://freedesktop.org/software/systemd/man/systemd.exec.html#id-1.20.4

While systemd has facilities to deal with this like SuccessExitStatus= it's far from ideal, as the status code lookup still shows 1/FAILURE. As exit 1 is the standard failure exit code for libc.

failed to find newNonce URL in directory

I'm not sure why it says this, because curling the directory shows it contains newNonce.

dcunnin@dcunnin:~$ uacme -v -c uacme.d issue fractaldemo.comy
uacme: version 1.0.18 starting on Wed, 04 Dec 2019 18:39:08 +0000
uacme: loading key from uacme.d/private/key.pem
uacme: loading key from uacme.d/private/fractaldemo.com/key.pem
uacme: checking existence and expiration of uacme.d/fractaldemo.com/cert.pem
uacme: uacme.d/fractaldemo.com/cert.pem does not exist
uacme: fetching directory at https://acme-v02.api.letsencrypt.org/directory
uacme: failed to find newNonce URL in directory
dcunnin@dcunnin:~$ curl -i https://acme-v02.api.letsencrypt.org/directory
HTTP/2 200 
server: nginx
date: Wed, 04 Dec 2019 18:39:41 GMT
content-type: application/json
content-length: 658
cache-control: public, max-age=0, no-cache
x-frame-options: DENY
strict-transport-security: max-age=604800

{
  "iiOi2bpTbTM": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": [
      "letsencrypt.org"
    ],
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
    "website": "https://letsencrypt.org"
  },
  "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
  "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
  "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}

Unpredictable behavior between `issue IDENTIFIER` and `issue CSRFILE`

I ran into an issue recently where running this command:

doas -u acme uacme -v www.domain.com

resulted in the somewhat cryptic message:

uacme: failed to stat www.domain.com: Permission denied

This was a result of running uacme as an unprivileged user while in a directory to which it has no read-access (ie /root), which then causes this call to fail, since uacme doesn't know whether the single argument is for a CSR file or a domain name.

Would it be possible to check if the stat error is due to permission denied instead of just a missing file, or alternatively add an additional command-line flag to force uacme to treat the input as a domain name instead of a file? I think it would at least be good to have a better error message, since I assumed it was due to uacme not having access to one of the directories it needed for writing the certificates.

Potential memory allocation issue in read-file.c

You may be interested that compiling uacme with gcc v7 and
Using built-in specs. COLLECT_GCC=/opt/local/gcc7/bin/cc COLLECT_LTO_WRAPPER=/opt/local/gcc7/libexec/gcc/x86_64-sun-solaris2.11/7.4.0/lto-wrapper Target: x86_64-sun-solaris2.11
give diagnostic from realloc.
read-file.c:119:14: warning: argument 2 value '18446744073709551615' exceeds maximum object size 9223372036854775807 [-Walloc-size-larger-than=] if (!(new_buf = realloc (buf, alloc)))

Allow creating private keys and directory with g+rX

I have various users on my system that need access to ssl private keys, so I use group ssl-cert for them, but uacme always sets umask such that private/* and key.pem files all end up not group readable. Would be nice if there were an option to allow group reading (setting correct group is handled because I have g+s on private/ in my case)

add option to define custom certificate subdirectory (?)

Hi,

would you consider support for an option by which the default "IDENTIFIER" part in determining the storage location for the certificate to be issued can be replaced by a custom, arbitrary value?

While it does probably make not too much sense to have multiple certificates with the same first identifier/common name, it is not strictly invalid and would not work well/at all with the default paths.

I'm in a situation where the actual certificate configuration is outside of my control and I would like to not have to restrict the choice of certificate (subject alternatives) names and at the same time avoid any potential collisions, which I currently do by using a different unique identifier.

PR using "-u" for this: #34

nsupdate.sh: ns_getdomain selects wrong server to send updates

I just came across uacme when finally getting around to move my dns-01 setup across from some ACMEv1-only software. Thanks for writing it - I like the balance of useful features without being over-complex. It seems I picked a good time because nsupdate.sh was added recently - I ran into a few things I needed to change to get that to work for me, I'm happy to split into PRs if you like but I thought I'd discuss here first.

When trying to identify which servers should receive updates, ns_getdomain picks the last two components of the domain name (foo.bar.example.org -> example.org). This is fine for simple "domain.tld" cases with no subdomains, but fails for country TLDs like .uk where many names are registered under .co.uk/.org.uk/.net.uk, or where a zone is delegated privately.

I'm using this instead, which I think should handle all cases, at the expense of another callout to dig:

ns_getdomain()
{
    local domain=$1

    [ -n "$domain" ] || return
    set -- $($DIG +noall +authority "$domain" SOA 2>/dev/null)

    echo $1
}

A couple of other smaller things I noticed,

  • the various calls to dig using the system's default nameserver use -k RNDC_KEY, which fails at least in some situations - should those be dropped and RNDC_KEY just used for nsupdate calls?

  • there's a bashism ${res//"/} in ns_ispresent but the hashbang line is for /bin/sh (and the other scripts look like plain shell). It could be rewritten like this, or would it be preferable to change to a bash #!?

    for NS in $nameservers; do
        OLDIFS="${IFS}"
        IFS='.'
        set -- $($DIG +short "@$NS" "$fqhn" TXT 2>/dev/null)
        IFS="${OLDIFS}"
        [ "$*" = "$expect" ] || return 1
    done
  • ns_gethostname has ${ret:1} which could be replaced with ${ret#?}, but the function is unused anyway, should it just be dropped?

FYI: httpd-challenge-hook.sh

You might be interested to know that I wrote a http-01 challenge hook script that is designed for the case when you don’t need/wanna continuously run a (full-blown) web server (e.g. domain for just OpenVPN, PostgreSQL, …). Read more…

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.