Domain Verification Protocol makes domain name verification as easy as verifying an email address or telephone number
Edit on GitLab

Specification

This document specifies version 1 of the Domain Verification protocol.

1. Terminology

The following terminology is used throughout this document:

Service Provider

An entity that provide products and services attached to domain names. Examples include SEO tools (e.g. Google Search Console), Social Media tools (e.g. Facebook Business Manager), Search Engine listings (Google Business Profile).

DNS Provider

An entity that provide DNS services – including registrars (e.g. GoDaddy) and standalone DNS services (e.g. CloudFlare).

User

An entity using a service provided by a Service Provider

Domain Owner

An entity with ownership and responsibility over a domain name

Verifiable Identifier

An identifier that a Service Provider can verify through automated means. For example: by sending a verification link to an email address; sending an SMS verification code to a phone number.

Domain Verification Record

A DNS TXT record for the Domain Verification protocol, identified as such by starting with the string @dv=N where N is a protocol version number.

Association Record

A DNS TXT record that associates an authorised party with a domain name.

Salt Reference Record

A DNS TXT record that lists Salt Store locations and Salt IDs, so that salts can be looked up.

2. Introduction

2.1. Problem

Domain verification is performed by hundreds of service providers, but it’s a complex process for users, requiring:

  • Access to website source files; or

  • Access to DNS records

Service providers issue instructions for users to verify domain names but these instructions are often beyond the technical capabilities of the layperson. This causes significant onboarding friction, customer support requests and can pose risk to the stability of the website, email or other services operating on the domain name.

2.2. Goal

The goal of the Domain Verification protocol is to make it as easy to verify a domain name as it is to verify an email address or telephone number.

2.3. Solution

The Domain Verification protocol uses DNS TXT records to create associations between a domain name and an authorised party. Authorised parties are identified using verifiable identifiers. Domain name registrars could automatically create Domain Verification records upon domain name registration, clearing a simple path for domain registrants to automatically verify domain names.

3. Privacy

By its nature, the Domain Verification protocol makes it possible to confirm an association between a verifiable identifier (e.g. email address/telephone number) and a domain name. The following steps have been taken to ensure this association is as private as possible:

3.1. SHA-256 Digest

Verifiable identifiers are only ever stored as SHA-256 digests, importantly these digests are not stored in a DNS TXT record using a standardised DNS name, where they could be harvested to facilitate hash breaking attempts. The digest is used as a DNS label within the DNS name – requiring a DNS query for each attempt to break the hash.

3.2. Salting

Optionally, verifiable identifiers can be salted before being hashed. This means that the association can only be verified when the email address, domain and salt are all known.

4. Verifiable Identifiers

This specification makes no determination about what should be considered a verifiable identifier – this protocol is intended for email addresses and telephone numbers, but could be used for any unique identifier that is verifiable.

4.1. Email Addresses

Email addresses must be trimmed of white space and downcased before being used as verifiable identifiers.

4.2. Telephone Numbers

Telephone numbers must be in E.164 format (e.g. +441234567890) before being used as verifiable identifiers.

5. DNS Names

The Domain Verification protocol uses DNS names to create and verify associations between authorised parties and domain names. Associations can be "hidden" or "secret":

5.1. Hidden Association

To create/verify a hidden association between the authorised party at user@example.com and domain name dvexample.com we use a DNS name with a base 36 encoded SHA-256 digest of the email address as the first label in the DNS name, and _dv as the second, like this: 4i7ozur385y5nsqoo0mg0mxv6t9333s2rarxrtvlpag1gsk8pg._dv.dvexample.com

A hidden association can only be verified by those who know where to look - they must know the domain name and email address, and suspect an association between the two.

5.2. Secret Association

To create/verify a secret association between the authorised party at user@example.com and domain name dvexample.com we use a DNS name with a base 36 encoded SHA-256 digest of the salted email address as the first label in the DNS name, and _dv as the second.

It is not sufficient to know only the email address and domain name to verify a secret association, the salt must also be known.

Assuming a salt of 0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Duco, the DNS name required to create/verify this association would be 6afvgus1jp324e7j06htlzy6zpn7iji80gihjp7cx1iaeb0nju._dv.dvexample.com

Secret associations require a salt to be set and a method to retrieve it.

5.2.1. Salt References

To enable secret associations for dvexample.com, a Salt Reference Record must be created/retreived using the DNS name _dv.dvexample.com

5.2.2. Salting Method

The salt is always added before the verifiable identifier (vid) e.g. #{salt}#{vid}.

Assuming a salt of: 0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Duco and vid of user@example.com the string to hash would be 0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Ducouser@example.com

6. Record Contents

Domain Verification records are written in CompactData, a JSON-like data serialisation language designed for efficient DNS storage. All records must start with @dv=X;, where X is a version number.

6.1. Association Record

The following keys are valid in an association record:

@dv

Required and must be the first key. This key identifies the DNS TXT record as a Domain Verification record. The value sets the protocol version number, the current protocol version number is 1.

h

Required. The [optionally, salted and] hashed verifiable indentifier. This enables a Domain Verification library to ensure the resource record is intended for the DNS name queried and not a wildcard resource record, or otherwise incorrectly created record.

s

Required. Service Types this associated party is authorised for. See Permissions.

p

Optional. Service Providers this associated party is authorised for. See Permissions.

sn

Optional. Services this associated party is authorised for. See Permissions.

d

Optional. A description of the authorised party this association relates to. Since the only reference to the authorised party is a hashed verifiable identifier, it can be helpful to store descriptive text to make it easier to maintain your Domain Verification records in the future. It is not recommended to store the verifiable identifier in clear text, since although you could assume anyone that can query the record already knows the verifiable identifier, the security of DNS transport varies depending on resolver implementation and resource records are routinely stored in resolver caches.

e

Optional. Expiry date in YYYY-MM-DD format. This sets an association expiry date, this is useful when granting a third party authorisation for temporary access.

6.2. Salt Reference Record

A Salt Reference record lists references for the salts used in secret associations for this domain:

@dv=1;salts=[(s=salts.domainverification.org;ids=[342c208d-0523-4d22-b7dd-32952dbeace2]);(s=example.com;ids=[90797a69-205b-4a35-88fe-8a186392ea15])]
  • @dv=1 – indicates this is a Domain Verification record and the version number

  • salts=[…​] – an array of salts used for Domain Verification records on this domain:

    • (s=salts.domainverification.org;ids=342c208d-0523-4d22-b7dd-32952dbeace2]) – a CompactData map containing two keys, one defining the Salt Store (s) and another defining the ids of the salts used.

    • (s=example.com;ids=[90797a69-205b-4a35-88fe-8a186392ea15]) – as above, but setting a different store and salt ID.

Using these references, we can lookup each salt with each salt store (see Salt Lookup).

6.3. Permissions

Permissions can be set by service type, provider and service name. Each permission is in addition to the last.

6.3.1. By Service Type

Permissions for service types are defined in an array using the key s, like this s=[seo;marketing], with the following values recognised:

  • all to grant permissions to all services.

  • seo to give permission for the domain to be verified for SEO-related services.

  • marketing to give permission for the domain to be verified for marketing services.

  • email to give permission for the domain to be verified for email services.

  • storage to give permission for the domain to be verified for online storage services.

6.3.2. By Service Provider

Permissions for service providers are in addition to any settings above and are defined in an array using the key p, like this p=[provider1.com;provider2.com].

6.3.3. By Service Name

Permissions for service names are in addition to any settings above and are defined in an array using the key sn, like this sn=[service1.provider3.com].

6.3.4. Example

For example, if a domain name owner wanted to give permission to their SEO agency to verify their domain, they could use the category seo to give their SEO agency the ability to verify the domain on a service that identifies itself as SEO-related, and use the service name analytics.google.com for Google Analytics. The Domain Verification record looks like this:

@dv=1;s=[seo];sn=[analytics.google.com]

6.4. Self Certification

It’s important to note that service providers certify themselves as a provider of a certain service type, or service name. Since the interests of domain owners and service providers are aligned – neither want someone to have incorrect permissions over a domain name – this is not expected to cause problems.

7. Record Lookup

Domain Verification records are found using DNS queries specifying the TXT record type.

7.1. Association Lookup

A query must be made for the hidden association record first using the specified DNS name for a Hidden Association:

dig 4i7ozur385y5nsqoo0mg0mxv6t9333s2rarxrtvlpag1gsk8pg._dv.dvexample.com TXT +short
-> "@dv=1;s=[all]"

If the query fails or an invalid record is returned, a failover query must be made for a secret association record. Before we can query for a secret association record, we must first make a Salt Reference Lookup and subsequent Salt Lookup.

Once we’ve retrieved the salt, we can query for a secret association using the specified DNS name for a Secret Association:

dig 6afvgus1jp324e7j06htlzy6zpn7iji80gihjp7cx1iaeb0nju._dv.dvexample.com TXT +short
-> "@dv=1;s=[all]"

If multiple salts have been used for secret associations on this domain, we should repeat this process for each salt in the order defined in the array, until a valid Domain Verification record is found or not.

7.2. Salt Reference Lookup

A salt reference lookup is made like this:

dig _dv.dvexample.com TXT +short
-> "@dv=1;salts=[(s=salts.domainverification.org;ids=[342c208d-0523-4d22-b7dd-32952dbeace2])]"

For each salt reference returned in the Salt Reference Record, we must perform a Salt Lookup.

8. Salt Lookup

Licensed service providers can lookup salts in our salt store using the salt lookup service available at salts.domainverification.org, other salt stores should follow the same simple format

9. Worked Example

In this worked example, we create associations between multiple authorised parties and the domain dvexample.com

9.1. Hidden Association

Let’s create a hidden association between the authorised party identified by the email address someone@example.com and the domain name dvexample.com and allow the authorised party to verify the domain for all marketing services and a service called hosting by the provider serviceprovider.com

9.1.1. Creating the Record

For a hidden association, we only need to create one Domain Verification record, the DNS name is derived as indicated by the following pseudo code representation:

SHA-256("someone@example.com")
-> 72497f475e4f76d0b28f57c73a084ece576d170874eba3ee2609d9afe4b71aab
Base-36("72497f475e4f76d0b28f57c73a084ece576d170874eba3ee2609d9afe4b71aab")
-> 2ujmt78p82bjs6asang9sy569ykmm1dcg171ssnhgjrh9wlmsr
Suffix with "._dv.dvexample.com"
-> 2ujmt78p82bjs6asang9sy569ykmm1dcg171ssnhgjrh9wlmsr._dv.dvexample.com

By creating a DNS TXT record at the above location we create an association between the verifiable identifier someone@example.com and the domain name dvexample.com, to set permissions for this authorised party to verify the domain on all services in the marketing category and a service named hosting for the provider serviceprovider.com we use the following content for the DNS TXT record:

@dv=1;s=[marketing];sn=[hosting.serviceprovider.com]

There’s more information about syntax for Permissions.

9.1.2. Performing the Lookup

To lookup a hidden association between the verifiable identifier someone@example.com and the domain name dvexample.com we query the same DNS name explained in the previous section:

dig 2ujmt78p82bjs6asang9sy569ykmm1dcg171ssnhgjrh9wlmsr._dv.dvexample.com TXT +short
-> "@dv=1;s=[marketing];sn=[hosting.serviceprovider.com]"

9.2. Secret Association

Let’s create a secret association between the authorised party identified by the email address anonymous@example.com and the domain name dvexample.com, and allow the authorised party to verify the domain for all services.

9.2.1. Creating the Records

For a secret association, we need to create two Domain Verification records. Firstly, we need to create a salt reference record. The contents of this record will depend on the tool used to create the record, the following example uses our tool to create a record.

We create the salt reference record at _dv.dvexample.com with the following content:

@dv=1;salts=[(s=salts.domainverification.org;ids=[342c208d-0523-4d22-b7dd-32952dbeace2])]

A second record is created using a DNS name derived from a SHA-256 digest of the salted verifiable identifier.

Assuming the salt is:

0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Duco

We prefix the salt to the verifiable identifier, hash the string and base 36 encode the digest as the first DNS label. We use _dv as the second label. The full DNS name is:

5zvt9zocn4iml3afmaesj1l1zid5d7mtgz8054r3kq2ilnhwed._dv.dvexample.com

The contents of the record are:

@dv=1;s=[all]

9.2.2. Using Mulitple Salt Stores

Sometimes your association records will be created by multiple parties and so you may need to store references for multiple salts. This is as simple as storing each in an array:

@dv=1;salts=[(s=salts.domainverification.org;ids=[342c208d-0523-4d22-b7dd-32952dbeace2]);(s=example.com;ids=[90797a69-205b-4a35-88fe-8a186392ea15])]

9.2.3. Performing the Lookups

The first step is to query the hidden association record (see Performing the Lookup), if that query fails or the returned record is invalid, the next step is to query the Salt Reference Record.

We query the salt reference record like this:

dig _dv.dvexample.com TXT +short
-> "@dv=1;salts=[(s=salts.domainverification.org;ids=[342c208d-0523-4d22-b7dd-32952dbeace2])

We take the salts array and work through each object to make a Salt Lookup, we send a HTTP POST to the address salts.domainverification.org and pass a token and saltID:

curl https://salts.domainverification.org -d "token=#{YOUR_TOKEN_HERE}&saltId=342c208d-0523-4d22-b7dd-32952dbeace2"
-> {"salt":"0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Duco"}

Now we have the domain (dvexample.com), the verifiable identifier (anonymous@example.com) and the salt used for secret association records on this domain (0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Duco), we can query for a secret association record using a DNS name derived as indicated by the following pseudo code representation:

Prefix "anonymous@example.com" with "0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Duco"
-> 0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Ducoanonymous@example.com
SHA-256("0E)W2!CohH2=?jF*5Sdjia4s(pnypXQZ3Cy!Ducoanonymous@example.com")
-> f807b5609eae64257bf4877652ea49fee40ac2451c152c12fa596ffeda647157
Base-36("f807b5609eae64257bf4877652ea49fee40ac2451c152c12fa596ffeda647157")
-> 66jpxxlzk088ws8q7pmwz72ck1626hlxd6txja5x01bfwhhnbr
Suffix with "._dv.dvexample.com"
-> 66jpxxlzk088ws8q7pmwz72ck1626hlxd6txja5x01bfwhhnbr._dv.dvexample.com

To verify the secret association and fetch permissions, we query the record in DNS:

dig 66jpxxlzk088ws8q7pmwz72ck1626hlxd6txja5x01bfwhhnbr._dv.dvexample.com TXT +short
-> "@dv=1;s=[all]"