buffrr / letsdane Goto Github PK
View Code? Open in Web Editor NEW๐ Let's DANE is an experimental way to enable the use of DANE/TLSA in browsers and other apps using a lightweight proxy.
License: Apache License 2.0
๐ Let's DANE is an experimental way to enable the use of DANE/TLSA in browsers and other apps using a lightweight proxy.
License: Apache License 2.0
We can afford to be more picky about TLS cipher suites for DANE secured connections. Go's TLS stack is for the most part on par with web browsers but we can be even more picky than web browsers and reject some weak legacy ciphers.
I was thinking of bumping min TLS version to 1.3 but this may be too aggressive since some linux distros may still use older versions of openssl/nginx. Although, sites using fancy technologies like DANE today should probably be using TLS 1.3.
For now allowing only these cipher suites for TLS 1.2 seems appropriate (as of Go 1.17 I don't think the order is important):
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
}
Do subdomains work?
I got this error from https://pinheadmz.proofofconcept.
Websites prove their identity via certificates. Firefox does not trust this site because it uses a certificate that is not valid for pinheadmz.proofofconcept.. The certificate is only valid for the following names: proofofconcept, *.proofofconcept, 142.93.115.133
Error code: MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT
... but the TLD-only URL worked fine: https://proofofconcept/
Could be my certificate parameters but I was hoping *.proofofconcept
would work.
I use letsdane on android (well, the proxy runs on a raspi). All apps and browsers work fine. DANE for Handshake names also works. But the Discord app doesn't. It's always on connecting. Could be a websocket thing or some kind of pinning, but I thought -skip-icann
doesn't modify anything.
Will try to debug this more, just wanted to post it here first if you had any ideas.
Add ability to use a username+password combo to protect the dane proxy server.
Hi @buffrr
Thanks for awesome library!
If we are going to continue to develop this library, we'll have to increase Go version at some point.
Do you have plan to use latest Go version 1.21.
Support for digest algorithm agility is optional but it's still useful if multiple digest algorithms are present.
Hi, This isn't an issue, just a suggestion for your README. And a shameless plug.
I expect most website admins wouldn't know how to implement DANE for their websites. It's usually only of interest to mail admins. And the learning curve is a big thing. And the payoff (for website admins) is tiny, so there's not a lot of incentive.
I've just published https://github.com/raforg/danectl which makes it super easy to use certbot to create a current + next pair of certificates, configure and generate all the TLSA 3 1 1 records you need (mail/web/whatever) for you to publish to the DNS, check that they are published, and perform rollovers.
It makes implementing DANE so easy that it might be enough to convince website admins to give it a go. Especially if they're already using certbot.
Of course, DNSSEC has to come first, but apparently 20% of new .com domains have DNSSEC, and with bind-9.16+ (like on the new stable debian-11), it has finally become incredibly easy to implement DNSSEC (only one extra line of config!).
But feel free to ignore this.
After performing static analysis using golangci-lint, there are some code to improve.
Fixing these will make letsdane a better library.
golangci-lint run ./...
proxy/conn.go:48:10: Error return value of `io.Copy` is not checked (errcheck)
io.Copy(src, dst)
^
proxy/conn_test.go:22:19: Error return value of `client.Handshake` is not checked (errcheck)
client.Handshake()
^
proxy/conn_test.go:40:14: Error return value of `(*crypto/tls.Conn).Handshake` is not checked (errcheck)
}).Handshake()
^
proxy/proxy_test.go:18:10: Error return value of `w.Write` is not checked (errcheck)
w.Write([]byte("hey"))
^
proxy/proxy_test.go:76:19: Error return value of `clientConn.Write` is not checked (errcheck)
clientConn.Write([]byte("bar"))
^
proxy/proxy_test.go:110:11: Error return value of `w.Write` is not checked (errcheck)
w.Write([]byte("hello"))
^
dialer_test.go:16:11: Error return value of `wr.Write` is not checked (errcheck)
wr.Write([]byte("foo"))
^
dialer_test.go:38:12: Error return value of `conn.Write` is not checked (errcheck)
conn.Write([]byte("GET / HTTP/1.1\nHost:example.org\n\r\n\r"))
^
tls.go:71:18: Error return value of `conn.SetDeadline` is not checked (errcheck)
conn.SetDeadline(time.Now().Add(time.Second * 3))
^
tls.go:81:21: Error return value of `clientTLS.Handshake` is not checked (errcheck)
clientTLS.Handshake()
^
tunnel.go:250:10: Error return value of `io.Copy` is not checked (errcheck)
io.Copy(src, dst)
^
tunnel_test.go:43:10: Error return value of `w.Write` is not checked (errcheck)
w.Write([]byte("foo"))
^
tunnel_test.go:104:14: Error return value of `conn.Write` is not checked (errcheck)
conn.Write([]byte(fmt.Sprintf("GET %s HTTP/1.1\nHost:%s\nConnection:close\n\r\n\r", test.uri, test.host)))
^
tunnel_test.go:132:11: Error return value of `wr.Write` is not checked (errcheck)
wr.Write([]byte("foo"))
^
tunnel_test.go:141:14: Error return value of `conn.Write` is not checked (errcheck)
conn.Write([]byte("ALPN TEST"))
^
cmd/letsdane/main.go:86:14: Error return value of `pem.Encode` is not checked (errcheck)
pem.Encode(certOut, &pem.Block{
^
cmd/letsdane/main.go:92:14: Error return value of `pem.Encode` is not checked (errcheck)
pem.Encode(privOut, &pem.Block{
^
cmd/letsdane/main.go:103:14: Error return value of `kOut.Write` is not checked (errcheck)
kOut.Write(privOut.Bytes())
^
resolver/resolver_test.go:403:6: func `rrsEq` is unused (unused)
func rrsEq(slice1, slice2 []dns.RR) bool {
^
cert.go:34:2: field `getCertificate` is unused (unused)
getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
^
resolver/resolver_test.go:361:10: S1034: assigning the result of this type assertion to a variable (switch rr := rr.(type)) could eliminate type assertions in switch cases (gosimple)
switch rr.(type) {
^
resolver/resolver_test.go:363:18: S1034(related information): could eliminate this type assertion (gosimple)
f = append(f, rr.(*dns.TLSA))
^
resolver/stub_test.go:82:5: testinggoroutine: call to (*T).Fatal from a non-test goroutine (govet)
t.Fatal(err)
^
resolver/stub_test.go:87:5: testinggoroutine: call to (*T).Fatal from a non-test goroutine (govet)
t.Fatal("dnssec-failed.org returned a valid response")
^
resolver/stub_test.go:92:5: testinggoroutine: call to (*T).Fatal from a non-test goroutine (govet)
t.Fatal(err)
^
resolver/stub_test.go:96:5: testinggoroutine: call to (*T).Fatalf from a non-test goroutine (govet)
t.Fatalf("got no ips")
^
resolver/stub_test.go:105:5: testinggoroutine: call to (*T).Fatalf from a non-test goroutine (govet)
t.Fatalf("got no tlsa records from freebsd.org")
^
tunnel_test.go:373:10: ineffectual assignment to err (ineffassign)
body, err := io.ReadAll(resp.Body)
^
cert_test.go:89:2: SA4006: this value of `tlsc` is never used (staticcheck)
tlsc, err = conf.GetCertificate(clientHello)
^
cert_test.go:96:2: SA4006: this value of `tlsc` is never used (staticcheck)
tlsc, err = conf.GetCertificate(clientHello)
^
cmd/letsdane/main.go:124:5: SA1019: x509.IsEncryptedPEMBlock has been deprecated since Go 1.16 because it shouldn't be used: Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. (staticcheck)
if x509.IsEncryptedPEMBlock(block) {
^
cmd/letsdane/main.go:129:25: SA1019: x509.DecryptPEMBlock has been deprecated since Go 1.16 because it shouldn't be used: Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. (staticcheck)
decryptedBlock, err = x509.DecryptPEMBlock(block, []byte(*pass))
^
resolver/stub_test.go:77:3: SA2002: the goroutine calls T.Fatal, which must be called in the same goroutine as the test (staticcheck)
go func(proto string) {
^
Is their a way to use a DoT server with LetsDANE?
I've got letsdane working (it's great, thanks @buffrr!) and it normally works fine. But when the name has a trailing dot, it doesn't.
Not a major problem, was just curious why this happens. (https://google.com. redirects normally)
unbound doesn't support DoH so a common error is:
$ letsdane -r https://1.1.1.1
This crashes with
panic: runtime error: invalid memory address or nil pointer dereference
Since it tries to clean up the unbound instance even though it has not been created yet. It should show the actual error given by libunbound instead.
I've been experimenting with using the LetsDANE http proxy to connect to a VPN server, but I end up running into errors when trying to validate SSL. (Both with and without a reverse proxy)
The only noticeable error I received was this:
C0CC8B6670000000:error:0A000126:SSL routines:ssl3_read_n:unexpected eof while reading:ssl/record/rec_layer_s3.c:321:
This is the error that occurred without using reverse proxy - The VPN server has the cert set directly through the VPN software, and then I specified the IP address of the VPN server in the DNS for the Handshake domain.
Please add support for using SOCKS5
The below is an effort to build letsdane in FreeBSD.
letsdane/cmd/letsdane # go build -tags unbound
/go/pkg/mod/github.com/miekg/[email protected]/unbound.go:36:10: fatal error: 'unbound.h' file not found
#include <unbound.h>
^~~~~~~~~~~
1 error generated.
/letsdane/cmd/letsdane # locate unbound.
/usr/local/lib/libunbound.a
/usr/local/include/unbound.h
/usr/local/lib/libunbound.so
......
......
......
Any suggestions on how to point at the location of the unbound.h? Thanks for your prompt response to the "IP_Addr:Port" issue. If you would want to include it in the README in addition to the "-r" and referral to the help, that would be clearer.
Some nameservers timeout or return SERVFAIL for any record type they don't understand
An example of such a server found in the wild (at the time of writing)
dig @dns1.tribpub.com _443._tcp.www.chicagotribune.com tlsa
This nameserver doesn't even understand DNSSEC, but a recursive DNSSEC resolver will return SERVFAIL in this case which is not an acceptable answer for DANE and the website breaks.
A DANE client should not expect that all nameservers will answer reliably for the TLSA record type.
To avoid breaking services that use such nameservers, we should:
Credits to @vdukhovni for telling me about this idea
./letsdane
Add Let's DANE proxy to your web browser 127.0.0.1:8080
T
If you could you make it possible to set the IP_Addr and Port as (additional) arguments at which the service runs, that would be lovely.
Thank you for this interesting project.
While PKIX certificate usages are optional, for complete DANE implementation we should support DANE-TA(2). This is useful if server administrators that would like to pin self-signed CA instead of pinning an individual end entity certificate for each service.
From RFC7671
Some domains may prefer to avoid the operational complexity of
publishing unique TLSA RRs for each TLS service. If the domain
employs a common issuing CA to create certificates for multiple TLS
services, it may be simpler to publish the issuing authority as a TA
for the certificate chains of all relevant services. The TLSA query
domain (TLSA base domain with port and protocol prefix labels) for
each service issued by the same TA may then be set to a CNAME alias
that points to a common TLSA RRset that matches the TA
Roughly 1 in 3 requests I make through this result in a long pause and then a timeout error. The interesting thing I've found is that while letsdane isn't responding I'm able to get rpc responses from hsd so it doesn't seem like an issue with hsd.
all the sudden , app crashed and now is impossible to start service, unless i hit allow , why now after all this time?
Static Binaries for each platform can be built using:
Static Binaries for Linux can be made using the following Dockerfile (or running the commands in Dockerfile manually).
# DOCKER_BUILDKIT=1 docker build -f static.Dockerfile -o build/ .
FROM golang:alpine AS base
RUN apk update && apk add linux-headers gcc make perl musl-dev expat-dev openssl-libs-static openssl-dev
#Install Unbound
FROM base as setup-unbound
WORKDIR /tmp
RUN wget https://www.nlnetlabs.nl/downloads/unbound/unbound-1.13.1.tar.gz && tar -xzf unbound-1.13.1.tar.gz
WORKDIR /tmp/unbound-1.13.1
RUN ./configure --prefix=/opt/install
RUN make && make install
FROM base as builder
#Install Dependencies
COPY --from=setup-unbound /opt/install/ /usr/local
#Optionally export this layer and cache this permanently somewhere.
#Copy stuff overgo mod download
WORKDIR /tmp/dane
COPY go.mod /tmp/dane/go.mod
RUN go mod download
#Will allow caching dependencies in layers.
COPY . /tmp/dane/
WORKDIR /tmp/dane/cmd/letsdane
#Build Static
RUN go build -tags unbound --ldflags '-extldflags "-lunbound -lssl -lcrypto -static"'
FROM scratch
COPY --from=builder /tmp/dane/cmd/letsdane/letsdane /
ENTRYPOINT [ "letsdane" ]
Use DOCKER_BUILDKIT=1 docker build -f static.Dockerfile -o build/ .
to export the binary into build directory.
Running this first time can take >500 seconds (Mostly because of all the compiling that needs to be done for unbound
and openssl
), The Image layer can be exported to Dockerhub and used repeatedly if necessary. Subsequent builds should take <100 seconds.
Todo:
One issue with allowing users to trust a validating resolver over a secure channel is that the secure connection itself relies on WebPKI which takes away the advantages provided by DANE.
Pinning is typically not recommended by DoH providers as the public key of the certificate could change, which will eventually break clients' setup. Even though trusting a resolver to do the validation is not ideal or recommended it's still needed by some users that don't want to compile letsdane with unbound and don't have a validating resolver.
Maybe with DANE-TA(2) support (issue #10) we can allow pinning the CA of a DoH or DoT provider? The CA is less likely to change making the setup less brittle. The advantage of this is trusting a single CA used by the resolver instead of all CAs installed on the device.
Users still need to manually provide a TLSA record for the pinning (which can be optional)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.