# js-libp2p-example-auto-tls **Repository Path**: mirrors_libp2p/js-libp2p-example-auto-tls ## Basic Information - **Project Name**: js-libp2p-example-auto-tls - **Description**: How to get a TLS certificate automatically - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-14 - **Last Updated**: 2026-03-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # @libp2p/example-auto-tls [![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) [![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) [![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-examples.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-examples) [![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-examples/ci.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain) > How to get a TLS certificate automatically [Interplanetary Shipyard](https://ipshipyard.com/) operates a public-good DNS service that will answer [Acme DNS-01 Challenges](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) on behalf of any Internet user. This means that we can use a service such as [Let's Encrypt](https://letsencrypt.org/) to generate certificates we can use to upgrade our WebSocket transport listeners to a Secure WebSocket version automatically. ## AutoTLS flow The steps for obtaining a TLS certificate are: 1. Have a publicly routable listening address 2. Claim the `*..libp2p.direct` domain name by `POST`ing a message to [register.libp2p.direct](https://github.com/ipshipyard/p2p-forge?tab=readme-ov-file#submitting-challenge-records) 3. Contact an ACME provider to perform the [DNS-01](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) challenge and generate a certificate 4. Add HTTPS listeners to any relevant transports ## Setup 1. Clone this repo then install the dependencies of this example with `npm install` ## Instructions The requirements for using `libp2p.direct` are: 1. A publicly routable socket address 2. A WebSocket or TCP listener 3. The [identify](https://www.npmjs.com/package/@libp2p/identify) protocol ACME services normally have quite restrictive rate limits, so we'll configure a a persistent datastore and a keychain so we can reuse any generated certificates, their private keys and the PeerID it's tied to. Let's configure the relevant modules: ```js import { noise } from '@chainsafe/libp2p-noise' import { yamux } from '@chainsafe/libp2p-yamux' import { autoTLS } from '@ipshipyard/libp2p-auto-tls' import { loadOrCreateSelfKey } from '@libp2p/config' import { identify, identifyPush } from '@libp2p/identify' import { keychain } from '@libp2p/keychain' import { webSockets } from '@libp2p/websockets' import { LevelDatastore } from 'datastore-level' import { createLibp2p } from 'libp2p' const datastore = new LevelDatastore('./db') await datastore.open() const privateKey = await loadOrCreateSelfKey(datastore) const libp2p = await createLibp2p({ datastore, privateKey, addresses: { listen: [ '/ip4/0.0.0.0/tcp/0/ws', '/ip6/::/tcp/0/ws' ] }, transports: [ webSockets() ], connectionEncrypters: [ noise() ], streamMuxers: [ yamux() ], services: { autoTLS: autoTLS(), identify: identify(), identifyPush: identifyPush(), keychain: keychain() } }) ``` > [!TIP] > If you want to experiment without hitting ACME service rate limits, you can > set the `acmeDirectory` to a staging address, though be aware that any > certificates generated will be self-signed: > > ```diff > services: { > - autoTLS: autoTLS() > + autoTLS: autoTLS({ > + acmeDirectory: 'https://acme-staging-v02.api.letsencrypt.org/directory' > + }), > identify: identify(), > ``` ## Getting a publicly routable address If we start the node now, we'll notice that nothing happens. The reason is that we don't have a publicly routable address. ### Manual configuration If you know you have a publicly routable address, for example if you are deploying your app on a server or you have manually configured port forwarding on your router then you can add your transport addresses to the `appendAnnounce` config key: > [!IMPORTANT] > Multiaddrs added to `announce` or `appendAnnounce` will automatically be > verified as publicly dialable where possible, though note that the domain name > allocated by `libp2p.direct` will still need to be verified via AutoNAT or > auto-confirmation - see the next section on confirming dialable addresses for > more ```diff addresses: { listen: [ '/ip4/0.0.0.0/tcp/0/ws', '/ip6/::/tcp/0/ws' - ] + ], + appendAnnounce: [ + '/ip4/123.123.123.123/tcp/1234/ws' + ], }, transports: [ ``` ### Automatic configuration via UPnP If you a home user and your router supports configuring port forwarding via [UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play), you can use the [@libp2p/upnp-nat](https://www.npmjs.com/package/@libp2p/upnp-nat) module to automatically configure port forwarding for IPv4 and IPv6 networks: > [!IMPORTANT] > This will only work if your router supports configuring port forwarding via > UPnP and also has it enabled. > > ISP provided routers sometimes do not and most ship with UPnP disabled by > default - please check your router documentation for more information ```diff import { keychain } from '@libp2p/keychain' + import { uPnPNAT } from '@libp2p/upnp-nat' import { webSockets } from '@libp2p/websockets' ``` ```diff services: { autoTLS: autoTLS(), + upnp: uPnPNAT(), identify: identify(), ``` #### Dealing with unreliable routers By default `@libp2p/upnp-nat` will use [SSDP](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol) to locate your router on the network in order to use it to map ports and discover it's externally routable address. Some ISP-provided routers are underpowered and require rebooting before they will respond to `SSDP` search messages. If you know the URL to the device descriptor of your [Internet Gateway Device](https://openconnectivity.org/developer/specifications/upnp-resources/upnp/internet-gateway-device-igd-v-2-0/) you can specify it manually and skip the search: ```diff services: { autoTLS: autoTLS(), + upnp: uPnPNAT({ + gateways: [ + // manually specify URLs for device descriptor documents + // these can be IPv4 and/or IPv6 (if supported by your network) + 'http://192.168.1.1:8080/path/to/descriptor.xml' + 'http://[xx:xx:xx:xx]:8080/path/to/descriptor.xml' + ] + }), identify: identify(), ``` Note that UPnP will still need to be enabled to map external ports (IPv4) and/or to open pinholes in the firewall if necessary (IPv6). ### Confirming dialable addresses Designing distributed systems typically involves trying to trust other system components as little as possible. Systems with a lot of trust tend to not be [Byztantine fault](https://en.wikipedia.org/wiki/Byzantine_fault)-tolerant. So far we've introduced two components that we are implicitly trusting. One is `libp2p.direct` - we trust that it will configure the DNS records to answer the ACME DNS-01 challenge correctly, and we trust that the external address reported by our UPnP router is correct. By default libp2p will not broadcast any public address until it has been confirmed to be dialable. We can skip this and explicitly trust `libp2p.direct` and our router by auto-confirming the DNS mapping and the public IP address: ```diff services: { - autoTLS: autoTLS(), + autoTLS: autoTLS({ + // automatically mark *..libp2p.direct as routable + autoConfirmAddress: true + }), - upnp: uPnPNAT(), + upnp: uPnPNAT({ + // automatically mark any detected socket address as routable + autoConfirmAddress: true + }), identify: identify(), ``` To not trust these actors and instead require confirmation from multiple peers in different network segments that the addresses are, in fact dialable, we need to configure [@libp2p/autonat](https://www.npmjs.com/package/@libp2p/autonat). #### AutoNAT This requires a few more system components. The rough flow here is: 1. Acquire network peers that speak the `/libp2p/autonat/1.0.0` protocol 2. Ask peers from a range of networks to dial us back on a specific address 3. Mark the address as reachable/not reachable after enough responses are received To find network peers we need a peer routing system such as [@libp2p/kad-dht](https://www.npmjs.com/package/@libp2p/kad-dht) (One day we may be able to use a [lightweight HTTP alternative](https://github.com/ipfs/specs/pull/476) but that day is not today). So far we've only configured a WebSocket listener, but if we use the Amino flavour of KAD-DHT (e.g. the public IPFS network), we have to be aware of [the spread of supported transports](https://probelab.io/ipfs/amino/#dht-transport-distribution). Because the WebSockets transport is not common, we need to add the [@libp2p/tcp](https://www.npmjs.com/package/@libp2p/tcp) transport to increase our likelihood of being able to communicate with network peers. Finally we need to also use the [@libp2p/bootstrap](https://www.npmjs.com/package/@libp2p/bootstrap) module to connect to an initial set of peers that will let us start to fill our routing table and perform queries: ```diff import { autoTLS } from '@ipshipyard/libp2p-auto-tls' + import { bootstrap } from '@libp2p/bootstrap' import { loadOrCreateSelfKey } from '@libp2p/config' import { identify, identifyPush } from '@libp2p/identify' + import { kadDHT, removePrivateAddressesMapper } from '@libp2p/kad-dht' import { keychain } from '@libp2p/keychain' + import { ping } from '@libp2p/ping' + import { tcp } from '@libp2p/tcp' import { uPnPNAT } from '@libp2p/upnp-nat' ``` ```diff transports: [ + tcp() webSockets() ], ``` ```diff services: { autoNAT: autoNAT(), + aminoDHT: kadDHT({ + protocol: '/ipfs/kad/1.0.0', + peerInfoMapper: removePrivateAddressesMapper + }), + bootstrap: bootstrap({ + list: [ + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', + '/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8', + '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ' + ] + }), upnp: uPnPNAT(), identify: identify(), + ping: ping(), ``` If you are running on your own network which has better support for varied transports you may not need to add `@libp2p/tcp`. ## Putting it all together Phew, well done making it this far. If you are happy trusting the IP address assigned by your ISP and router, and that `libp2p.direct` has configured DNS for you correctly, you can run the working example in [./auto-confirm.js](./auto-confirm.js). If you want to go the full trust-free route, please see the example in [./trust-free.js](./trust-free.js). After a little while you should see multiaddr(s) that include the [Server name indication](https://en.wikipedia.org/wiki/Server_Name_Indication) tuple: ```console $ node ./trust-free.js /ip4/[ip-address]/tcp/[port]/tls/sni/ip-address.base32-peer-id.libp2p.direct/ws/p2p/12D3Foo ``` ## Need help? - Read the [js-libp2p documentation](https://github.com/libp2p/js-libp2p/tree/main/doc) - Check out the [js-libp2p API docs](https://libp2p.github.io/js-libp2p/) - Check out the [general libp2p documentation](https://docs.libp2p.io) for tips, how-tos and more - Read the [libp2p specs](https://github.com/libp2p/specs) - Ask a question on the [js-libp2p discussion board](https://github.com/libp2p/js-libp2p/discussions) ## License Licensed under either of - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) - MIT ([LICENSE-MIT](LICENSE-MIT) / ) ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.