Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPIP-296: DNSLink resolution export with DNSSEC records #296

Closed
wants to merge 10 commits into from
313 changes: 313 additions & 0 deletions http-gateways/DNSSEC_EXPORT_DNSLINK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
# IPIP 0000: Gateway-provided DNSSEC proofs for DNSLink responses
- Start Date: 2022-07-06
- Related Issues:
- https://github.com/ipfs/kubo/issues/8799
- https://github.com/ipfs/kubo/issues/6129

## Summary
IPFS is connected to DNS through a standard called DNSLink. DNSLink allows a DNS TXT record for a domain to point to an IPNS address or IPFS content ID (CID). This specification describes a method for providing proof to the requesting client that the DNS mapping is provided by honest/trusted name servers and that the IPFS address pointed to by the TXT record corresponds to the content received by the client.
iowaguy marked this conversation as resolved.
Show resolved Hide resolved

This specification consists of four logical pieces.

1. A method for serializing DNSSEC proofs and IPFS CAR files
2. A method for communicating these proofs to the requesting client
3. A method for the client to validate DNSSEC proofs
4. A method for the client to validate the integrity of the IPFS content

## Motivation
In the current gateway, clients are forced to trust the DNS and IPNS resolutions of the gateway. This entails implicit risk for clients who cannot validate if the content they receive is the content bound to the name they requested. This state of affairs can leave clients vulnerable to MitM attacks.

This proposal reduces the trust needed by a client, and allows honest gateways to prove it is responding truthfully.
iowaguy marked this conversation as resolved.
Show resolved Hide resolved

## Requirements & Assumptions
### Feature Requirements
1. The proof must be returned in-band with the DNS responses so that the gateway does not need to store state. This is not a hard requirement, but storing state is an engineering challenge that would be nice to avoid.
2. The proof must be verifiable by a client. This includes not being so large as to cause undue burden on the client.

### Compatibility Requirements
1. Participating clients should still be compatible with non-participating gateways. In other words, a client benefiting from this solution should still be compatible with all gateways that it can presently use.
2. Gateways should maintain compatibility with non-participating clients—those clients will not benefit from the DNSSEC proofs, but they should still have full access to data.
3. Gateways should not rely on any specific public DNS resolver, i.e., the implementation should be resolver agnostic.

iowaguy marked this conversation as resolved.
Show resolved Hide resolved
### Security Considerations
1. DNSSEC responses can be much larger that vanilla DNS responses, in theory, this could be a vector for DDoS amplification.

### Assumptions on Scope
1. We consider only IPFS blocks bound to DNS with DNSLink. This is without loss of generality, because an IPFS subgraph referenced by its root hash is directly verifiable by confirming the hashes and thus would not benefit from the DNSSEC proof.
2. For non-existent URLs, we will not walk the zone (through NSEC records). Including the walkthrough would dramatically increase the proof size. In other words, we will only offer verifiable proof for positive responses.
3. We will not support wildcards in DNS resolution. This is not a problem because our target subdomains will always begin with "\_dnslink", and domains cannot have wildcards in the middle.
4. We will not support multiple root Key Signing Keys or changed Key Signing Keys in this version.
iowaguy marked this conversation as resolved.
Show resolved Hide resolved

### Assumptions on resource usage
1. Informal experiments have shown that chains of DNSSEC responses are between 2-4 KB. This includes denial-of-existence proofs, even in zones that are known to have high DNSSEC adoption (where proofs can be larger).

## Threat Model
- DNS name servers are trusted to provide honest mappings.
- A resolver may attempt to break the chain of trust.
iowaguy marked this conversation as resolved.
Show resolved Hide resolved
- Cryptographic primitives are assumed to be secure.
- A gateway can attempt provide either a false DNSSEC proof or incorrect IPFS content and is therefore untrusted.

## Detailed Design
### Components
1. HTTPS-capable client (likely in a browser)
2. IPFS gateway
3. Recursive resolver
4. IPFS network

### How do the components work together
1. Client establishes a TLS connection to the gateway and sends a HTTP request to the IPFS gateway with header "Accept: application/vnd.ipld.namedcar" using a Reduced TLS DNSSEC Chain Extension (see below)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two nits:

  • As noted in later comments, we should not include CAR in the response.
  • If there is no data, and only DNSSEC/DNSLink proof, this can be named accordingly
  • Other parts of the document make note this is "Version 1", probably good idea to include an explicit version parameter here.
Suggested change
1. Client establishes a TLS connection to the gateway and sends a HTTP request to the IPFS gateway with header "Accept: application/vnd.ipld.namedcar" using a Reduced TLS DNSSEC Chain Extension (see below)
1. Client establishes a TLS connection to the gateway and sends a HTTP request to the IPFS gateway with header "Accept: application/vnd.dnslink.dnssec; version=1" using a Reduced TLS DNSSEC Chain Extension (see below)

2. Gateway queries recursive resolver for DNSSEC chain and DNS resolution, which should yield an IPFS content name (from a DNSLink)
3. Gateway requests CAR of content from IPFS network
4. Gateway returns IpfsDnssecChain (see data model) to client and also caches for future requests.


```mermaid
stateDiagram-v2
state "IPFS Network" as ipfs
Client --> Gateway: HTTP GET\n cloudflare-ifps.io/ipfs.example.com
iowaguy marked this conversation as resolved.
Show resolved Hide resolved
Gateway --> Client: IpfsDnssecChain
Gateway --> Resolver: DNSSEC query\n ipfs.example.com
Resolver --> Gateway: DNSSEC response\n /ipns/abc123
Gateway --> ipfs: ipfs get /ipns/abc123
ipfs --> Gateway: IPFS file
```

### Reduced TLS DNSSEC Chain Extension
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭
Needs clarification: since "Reduced TLS DNSSEC Chain Extension" here is based on RFC 9102, does it also include setting dnssec_chain flag on request's TLS handshake?

My feedback here is that custom TLS is a very big ask for everyone to implement, and limits the utility of DNSLink proofs on Gateways, especially given they can be decoupled from retrieving content-addressed data. Ok to experiment, but we should always have a plain way for fetching them without messing with TLS layer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Reduced TLS DNSSEC Chain Extension is an experimental RFC, which has inspired some of the design in this IPIP. There is no ask for gateways to implement a custom TLS layer.

The protocol, as defined in [RFC 9102](https://datatracker.ietf.org/doc/html/rfc9102), is overkill for our use case. We do not require the chain of authentication (TLSA records and signatures), only the chain of DNSSEC signatures. This proposed extension would entail reusing the RFC, but cutting out any TLSA bits. The details of this serialization are detailed below.

### DNSSEC Proof Validation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭
We have browser vendors (un)officially interested in adding end-to-end integrity "checkmark" to DNSLink websites loaded over IPFS.
Someone familiar with implementation cost on the browser side should take a look (will ping some folks).


We describe the proof validation process as a finite state machine, taking inspiration from the RFC draft: Serializing DNS Records with DNSSEC Authentication. The naming terminology (e.g. ENTERING, LEAVING) describes the traversal of zones during validation. Each zone has a name and during validation we track a set of trusted keys for each zone that we store in a stack of zones. For field names, see Data Model. We assume that the validator knows the target domain they are attempting to access, we refer to this as target.

The protocol begins in the INITIAL state. The validator will have received an initial\_key\_tag (see RFC 4034) from the gateway. The validator uses this to confirm that it is using the correct initial key. For version 1, we are using zero to indicate that we're using the root zone's public Key Signing Key (KSK), and not supporting any other roots of trust. Non-zero values should prompt a failure. The root zone's KSK is trusted by default. Subsequently, the protocol moves to the ENTERING state.

In the ENTERING state, the validator reads the next unread Entering. The validator then assembles the Entering's keys in canonical form using canonical ordering (RFC 4035, Section 5.3.2). Then the validator checks the signature on the current Entering's key set using the trusted KSK for the current zone. This will either be the root zone's KSK, or a KSK check against a DS in the previous LEAVING state. If the check succeeds, the entire key set can be trusted for the current zone. The validator then extracts the Key corresponding to the entry\_key\_index, and confirms that bit 7 is set indicating that this is a Zone Signing Key (ZSK) and fails otherwise. Then the protocol moves into the LEAVING state.

The LEAVING state begins by reading the next unread Leaving struct. There are four possible types of Leaving structs: CNAMEs, DNAMEs, DSs, and defaults. If the RRType is anything except DS, CNAME, or DNAME, the validator assembles the RRDATA in canonical form and in canonical ordering (the same way as was done in ENTERING). It then checks it against the signature in Signature using the trusted ZSK (the entry key) for the current zone. If the signatures do not match, the protocol fails. Otherwise, if next\_name matches target, validation has been successful. The final text record must be at a subdomain that begins with "_dnslink" according to the DNSLink standard. Since we have a hard constraint on the beginning of the domain, we will not support wildcards, which cannot occur in the middle of a domain. The protocol moves to ACCEPT.

Within the LEAVING state, if the RRType is DS, the validator must check that there is at least one record in ds\_records where the digest equals the hash of the KSK in the subsequent Entering struct. The validator then checks that the next\_name has a longer matching prefix[^1] with the target than the current zone. The protocol then moves back to ENTERING. If the RRType is CNAME, the validator must confirm that next\_name matches target, and fail otherwise. This must be the case because a CNAME can only be used at the leaf subdomain in the DNS tree hierarchy. If next\_name does match target, then name becomes the new target. We pop zones off the stack until the new target is within the topmost zone the reenter the LEAVING state. If the RRType is DNAME, the validator must confirm that next\_name is a super-domain of target, and fail otherwise. If next\_name is a super-domain of target, then the next\_name suffix in target is replaced by name. We then pop zones off the stack until the new target is within the topmost zone and then reenter the LEAVING state. This method supports DNAME chaining.

[^1] A longer match in terms of number of subdomains/domains.
iowaguy marked this conversation as resolved.
Show resolved Hide resolved

```mermaid
stateDiagram-v2
INITIAL --> ENTERING: initial_key_tag
ENTERING --> LEAVING
LEAVING --> ENTERING: DS
LEAVING --> LEAVING: CNAME || DNAME
LEAVING --> FAIL
ENTERING --> FAIL
LEAVING --> ACCEPT: TXT
```

### CAR File Validation
This can be done in parallel with the DNSSEC proof validation. We again describe the procedure in terms of a finite state machine. Note that the CAR format supports any hash algorithm that can be encoded in the multihash format. For version 1, we will only support a few common algorithms (e.g., SHA256)—this should not cause any compatibility issues, since the gateway is requesting the CAR file (and thus declaring what hash algorithm the CAR should use).


Validation begins in the INITIAL state. Upon reading the CAR header, we extract the CIDs and payloads of each block in the CAR (stored in a list), then enter the VALIDATE\_HASH state. In VALIDATE\_HASH, we check that the client has support for the hash formats and codecs of the current block (and fail if not). We then hash the block payload and check that it matches the CID. If they do not match, validation fails. If they do match, we move to the next block in the list and reenter the VALIDATE\_HASH state. If there are no more blocks, transition to ACCEPT.


```mermaid
stateDiagram-v2
INITIAL --> VALIDATE_HASH: Parse blocks, [(CID, payload)]
INITIAL --> FAIL: Cannot parse CAR
VALIDATE_HASH --> VALIDATE_HASH: CID == hash(payload)
VALIDATE_HASH --> FAIL: CID != hash(payload)
VALIDATE_HASH --> ACCEPT
```
For the client implementation, we will prefer to use an available library. Both Go and JS libraries are available (i.e., go-car and js-car). If we opt for the browser extension design, the JS library is probably the best option.

### Alternative Design
For each request, the gateway would respond with an additional header of the form X-Ipfs-Name-Proof: <CID>, where <CID> is a pointer to an IPFS file containing the DNSSEC proof. A client could then retrieve this proof directly from IPFS if they want it. However, we decided against this approach for several reasons. The main reason is that it is never more advantageous for the gateway to retrieve the DNSSEC proof from IPFS than it would be to get it from a local cache. The only time this would be useful is if a gateway could use DNSSEC proofs found by other gateways. This is not feasible because the DNSSEC proof would be stored according to its hash (in IPFS), and the hash required can only be known by already collecting the DNSSEC proof—therefore it would be pointless for a gateway to request content that, by definition, it already has. An additional problem is that DNSSEC proofs have TTLs, and thus will not usually be valid for more than an hour. This limits the time that the content would be useful anyway.
Comment on lines +120 to +121
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a variant of this design is worth evaluating.

I propose we remove the need for generating the proof and returning CID with every response on DNSLink websites, and instead, leave it up to the client to request DNSSEC proof via a separate request, and response having ONLY the DNSLink's DNSSEC proof.

This way:

  • gateways don't need to do proofs unless client explicitly requests one
  • simplifies the spec and client implementation by decoupling DNSSEC proofs from CAR responses
  • saves bandwidth, as the proof is not send with EVERY CAR
    • client is fetching proof once for dnslink=/ipfs/{cid} and caches it (e.g. based on DNS record TTL)
    • as long it is in cache, the proof can be reused for all requests made to DNSLink website because we already trust the (DNS → root CID) mapping

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having the resolution in two steps require the gateway to keep some state, which I don't think is a good requirement. Ideally, gateway should be stateless. In the variant you propose above, a stateless gateway could be consider dishonest as it might not necessarily have the same view of DNS in the two requests.

For a two requests protocol to not require the gateway to have state, a client should first request the DNSSEC proof, and then request the resolved CID/CAR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, the gateway doesn't need to request the proofs from the resolver unless it receives the header "Accept: application/vnd.dnslink.dnssec; version=1". So a client who's only interested in the CAR can opt to receive only that.

Copy link
Member

@lidel lidel Aug 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree on two points: the gateway should be stateless, and the client should be able to decide when the DNSLink proof is returned.

What I'm not comfortable with is limiting clients, forcing them to always fetch data when all they want is DNSLink proofs. Having 2-in-1 response type is fine (sounds like you have use cases in mind for this?), but there should also be a way to request proof without the data, allowing clients to do the data retrieval by other means.

For a two requests protocol to not require the gateway to have state, a client should first request the DNSSEC proof, and then request the resolved CID/CAR.

This is exactly that I had in mind, and it follows the patterns of how the web works.
You need to resolve DNSLink once, and in my mind DNSSEC proof would be part of that.
After that, you don't need it again until TTL runs out.




## Data Model
In this section we define the data model and structures that define the serialization format. All data will be serialized in the order specified in the following structs, without padding. All multi-byte integers are encoded big-endian. The top-level structure is IpfsDnssecChain, all data necessary for a client to validate the resolution's correctness is nested within this structure.

```c
struct IpfsDnssecChain {
uint8 version;
DnssecProof proof;
Car ipfsData;
}
Comment on lines +129 to +133
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑
Putting everything into a single response may not be the best design:

  • it couples DNSSEC proofs to the content-addressed data, which probably makes sense in client-server model of the current web, but makes it impossible for IPFS clients to benefit from content-addressing e.g.
    • client should be able to fetch different parts of content-addressed data from multiple sources over multiple transports,
    • or at the very least, client should be able to resume failed download using a different gateway

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coupling has the advantage of not requiring state, nor a two requests protocol from the client side. Similar to CAR which block headers could technically be requested separately, having them all bundled in one file is useful.

  • It allows the state to be consistent when generating the bundle, and when validating it.
  • Caching is also simpler as there is only one DNS/IPNS/etc view that prevails: the gateway view.

Regarding the decoupling, one could imagine a DAG build on top of IpfsDnssecChain with a node for version, a node for proof, and a node for ipfsData. Data retrieval is different from the bundling. One will likely not be able to resume download of a DNSLink proof on a gateway with a different view, but once the bundle has been generated, it can be content addressed.

Copy link
Member

@lidel lidel Aug 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to CAR which block headers could technically be requested separately, having them all bundled in one file is useful.

Mind elaborating on use cases you have in mind for coupling them together?
Maybe it will make the utility of this approach more apparent.

I was trying to imagine mobile web browser, or a light IoT client, in both cases I struggle to see benefit from sending DNS+DNSSEC resolution along with the data.
To maximize cache hits, client would resolve mutable pointer (DNS or IPNS) to learn the CID, then immutable data is transferred.
Caching immutable data together with the pointer does not leverage the fact that immutable data can be placed in a global cache to maximize cache hits across different mutable pointers and time.

One will likely not be able to resume download of a DNSLink proof on a gateway with a different view

Why not? Client resolved DNSLink to a CID. CID is either a single block, or a root of a bigger DAG (e.g., UnixFS directory,).

If that CID is a root of a DAG, download can be not only resumed, but download of different branches of the DAG can be fetched from different gateways, or even via P2P transports.

```

Note that simply including both the DNSSEC chain and the IPFS CAR in the response is sufficiently strong binding for this use case. This is because the last entry in the DNSSEC chain will include a signed DNSLink record, which validates the IPFS root hash, meanwhile, the client can directly confirm that that CAR is correct by hashing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️
I feel this is exactly the reason, why this spec should not couple data and proofs together.
All that the client cares about is the integrity of dnslink=.. string in DNS TXT record.

How the content-addressed data is fetched should not be in the scope here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @thibmeu's comments apply here as well. If the binding fails to validate, it would not be clear if the gateway was dishonest or if the DNSLink mapping changed.


The following structures are for serializing IPFS files. In Car, the pragma field is a series of "magic bytes" that indicates the format version (we will support version 2, the latest, which is compatible with version 1). Version 2 gives us more flexibility for indexing the payload than with version 1, which does not natively support indexing. Though, for the first version, we will likely not need indexing. Version 2 is a wrapper around the entire version 1 archive.

```c
struct Car {
bytes pragma[11];
CarHeaderV2 head;
CarV1 data;
IndexPayload index;
}

struct CarHeaderV2 {
byte characteristics[16];
byte dataOffset[8];
byte dataSize[8];
byte indexOffset[8];
}

struct IndexPayload {
uint32 length;
bytes payload[length];
}
```

CarHeaderV1 is encoded in DAG-CBOR per the IPLD spec. The type varint is an alias for an unsigned LEB128 integer.

```c
struct CarV1 {
CarHeaderV1 head;
CarBlockV1 blocks[];
}

struct CarHeaderV1 {
varint version; // will always be 1
}

struct CarBlockV1 {
varint length;
Cid root;
byte data[];
}

struct Cid {
byte address[];
}
```

For version 1, we will only support the initial\_key\_tag pointing to the root zone's public key. A value of zero for initial\_key\_tag will confirm this. Non-zero values will cause the verifier to reject the proof. The array of ZonePairs should be in order from the root zone, down to the zone hosting the record of the initial request. Every ZonePair must have a valid Leaving, but need not contain an Entering (the reason for this becomes clear when we discuss CNAMEs and DNAMEs during validation).

```c
struct DnssecProof {
uint16 initial_key_tag;
uint8 num_zones;
ZonePair zones[num_zones];
}
Comment on lines +187 to +191
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭
If we decouple content-addressed data from DNSSEC proof, then THIS alone could be what the client receives when they request application/vnd.dnslink.dnssec; version=1.

Ensuring this struct is useful to clients (such as web browsers) should be our main focus.

Reusing existing RFC formats where possible is 👍
Will ping some browser folks for review here.


struct ZonePair {
Entering entering;
Leaving leaving;
}

enum ZoneRecType {
ENTERING = 0;
LEAVING = 1;
}
```

The entry\_key\_index corresponds to a key in keys. In the initial zone (i.e. root zone), the entry key will have length zero. If it is non-zero, this should prompt failure until non-root zone trust anchors are supported. The signature is over the key set (both ZSKs and KSKs) at the present zone.

```c
struct Entering {
uint16 length;
ZoneRecType type;
uint8 entry_key_index;
struct Signature key_sig;
uint8 num_keys;
struct Key keys[num_keys];
}
```

Resource records should be ordered as in RFC 4034 (Section 6.3). The next_name field stores the name of the next zone in the validation.

```c
struct Leaving {
uint16 length;
ZoneRecType type;
string next_name;
RRType rrtype;
struct Signature rrsig;

select (rrtype) {
case CNAME:
Name name;
case DNAME:
Name name;
case DS:
uint8 num_ds;
struct Ds ds_records[num_ds];
default:
uint8 num_rrs;
struct RRData rrs[num_rrs];
}
}
}

enum RRType {
NS = 0,
DNSKEY = 1,
RRSIG = 2,
DS = 3,
CNAME = 4,
DNAME = 5,
TXT = 6
}

struct RRData {
uint16 length;
byte rrdata[length];
}
```

Keys are formatted exactly as in RFC 4034 (Section 2.1), and ordered as in Section 6.3.

```c
struct Key {
uint16 length;
Dnskey_Rdata rdata[length];
}

struct Dnskey_Rdata {
uint16 flags;
uint8 protocol;
uinit8 algorithm;
byte public_key[];
}
```

This struct represents the signature and associated metadata.


```c
struct Signature {
uint16 length;
uint8 algorithm;
uint8 labels;
uint32 ttl;
uint32 expires;
uint32 begins;
uint16 key_tag;
byte signature[length - 16];
}
```

This struct holds the data needed to validate DS records. This format is the same as described in RFC 4034 (Section 5.1). According to [Serializing DNS Records with DNSSEC Authentication](https://www.ietf.org/archive/id/draft-agl-dane-serializechain-01.html#rfc.section.3), key\_tag and algorithm are redundant. However, inferring both of these increases complexity, so we will include them explicitly in version 1.

```c
struct Ds {
uint16 key_tag;
uint8 algorithm;
uint8 digest_type;
uint16 digest_len;
byte digest[digest_len];
}
```

### Security
DDoS Amplification: For each request to the gateway, a request must be made to the resolver and to IPFS, leading to several server-side computations. However, This information is cacheable, which should reduce the risk.
Comment on lines +302 to +303
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main concern here is that DNSLink's DNSSEC proofs should not be returned with every block or CAR.

The client needs to fetch the proof only once, and the proof, once generated, can be cached for some time. I think this spec should explicitly state that proof can be cached by the gateway for at least 60 seconds, and up to the TTL of the TXT record.

This should mitigate use of proof requests as a surface for DoS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requiring a minimum TTL could go against DNS behaviour, and adding a new layer of cache should be left to the gateway operator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I forgot we already talked about his around DNS.MaxCacheTTL.

Ignore my comment then, no special treatment here,
gateway and client should reuse DNS cache around TTL value for TXT record with DNSLink.



## Open Problems
The DNS query (via DNSLink) will often not resolve to a CID. It may resolve to either a CID with a subresource (e.g. /ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme) or it may resolve to an IPNS name (e.g. /ipns/QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd). Regarding subresources: our choice is either to have the client do the recursive resolution, or do the resolution on the server side. As the point of this spec is to avoid client side DNSSEC resolutions, it would probably make more sense to go with the second approach. However, this second approach may involve adding new headers to the CAR.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭
re: subresource, if we decouple data from proof response, the problem of subresources goes away: no need to trust gateway did resolution right, the client will fetch root CID block to learn about CID of subresource.


Regarding IPNS names: we can most likely serialize the IPNS proof using similar techniques as described above.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It would be good to clearly state the scope of this Version 1 spec.

Sidenote: context on recursive DNSLinks

If the DNSLink value is /ipfs/.. then the story is simple, we have two distinct stages of resolution and DNSSEC proof closes the gap, allowing clients to display end-to-end integrity indicators:

flowchart LR
    dnslink=/ipfs/CID-->/ipfs/CID
    subgraph DNSSEC
      example.com-->dnslink=/ipfs/CID
    end
    subgraph IPFS
    /ipfs/CID-->DAG
    DAG-->blocks
    end
Loading

If we add /ipns/ to the picture, that namespace requires an additional resolution step: /ipns/ path can point at an IPNS KEY or another domain name (FQDN), which requires doing recursive DNSLink resolution. This resolution creates more complexity and potential gap around client's ability to verify signed IPNS records (KEY ones):

To illustrate:

flowchart TB
    dnslink=/ipfs/CID-->/ipfs/CID
    FQDN-->dnslink=/ipns/KEY
    dnslink=/ipns/KEY-->/ipfs/CID
    dnslink=/ipns/KEY-->/ipfs/CID
    /ipns/FQDN-->FQDN
    /ipns/KEY-->/ipfs/CID
    
    subgraph DNSSEC
      FQDN-->dnslink=/ipfs/CID
      FQDN-->dnslink=/ipns/KEY
      FQDN-->dnslink=/ipns/FQDN
    end
    subgraph IPNS    
      dnslink=/ipns/KEY-->/ipfs/CID
      dnslink=/ipns/KEY-->/ipns/KEY
      dnslink=/ipns/KEY-->/ipns/FQDN
      dnslink=/ipns/FQDN-->/ipns/FQDN

     
    end
    subgraph IPFS
      /ipfs/CID-->CID
      CID-->DAG
      DAG-->blocks
    end
Loading

What to do with /ipns/ ?

For Version 1, we could say the /ipns/ gap is out of the scope and needs to be resolved client-side.

I think we don't need to worry about dnslink=/ipns/FQDN – client will be able to send follow-up DNSSEC proof requests for the new FQDN, and do that recursive dance until a non-DNS, cryptographically verifiable path is found.

Open problem / need for spec: fetching signed IPNS records from Gateway

I think Gateways should support a scenario where dnslink=/ipns/KEY.
Many clients won't be able to retrieve IPNS records (no p2p, no dht), and asking clients to implement delegated routing via separate protocol like reframe (allowing light clients to fetch Signed IPNS Records) may be a stretch for many.

I think the Gateway should provide a way for a client to request IPNS proof (signed IPNS record) in a way that is as easy as requesting a DNSSEC one.

Details TBD, but my initial take is that either

  • "IPNS proof" should be a separate request-response type, allowing clients to resolve and cache IPNS resolutions independently, or

  • part of unified application/vnd.dnslink.integrity; version=1 response that returns both DNSSEC proof and (optional) IPNS signed record, broad strokes:

    struct DnslinkIntegrity {
      DnssecProofs dnssecProofs;
      SignedIPNSRecords ipnsRecords;
    }
    struct DnssecProofs {
      uint32 length;
      IpfsDnssecChain chains[length];
    }
    struct SignedIPNSRecords {
      uint32 length;
      IPNSRecord records[length];
    }

To make things easier for light clients, we should lean towards the latter one. A single request-response made by a thin client that can be cached for the duration of the retrieval session.



## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).