diff --git a/README.md b/README.md
index 90c50e6..e068fa0 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,8 @@ This repo includes everything you need to work with DNSCrypt. You can run your o
* [Command-line tool](#commandline)
* [How to install](#install)
+ * [How to configure](#configure)
+ * [Converting dnscrypt-wrapper configuration](#convertfromwrapper)
* [Running a server](#runningserver)
* [Making lookups](#lookup)
* [Programming interface](#api)
@@ -28,12 +30,19 @@ Please note, that even though this tool can work as a server, it's purpose is me
Download and unpack an archive for your platform from the [latest release](https://github.com/ameshkov/dnscrypt/releases).
-### Running a server
+
+### How to configure
Generate a configuration file for running a DNSCrypt server:
-```shell script
-./dnscrypt generate --provider-name=2.dnscrypt-cert.example.org --out=config.yaml
+```
+./dnscrypt generate
+
+[generate command options]
+ -p, --provider-name= DNSCrypt provider name. Param is required.
+ -o, --out= Path to the resulting config file. Param is required.
+ -k, --private-key= Private key (hex-encoded)
+ -t, --ttl= Certificate time-to-live (seconds)
```
It will generate a configuration file that looks like this:
@@ -54,10 +63,34 @@ certificate_ttl: 0s
* `es_version` - crypto to use. Can be `1` (XSalsa20Poly1305) or `2` (XChacha20Poly1305).
* `certificate_ttl` - certificate time-to-live. By default it's set to `0` and in this case 1-year cert is generated. The certificate is generated on `dnscrypt` start-up and it will only be valid for the specified amount of time. You should periodically restart `dnscrypt` to rotate the cert.
+#### Converting [dnscrypt-wrapper](https://github.com/cofyc/dnscrypt-wrapper) configuration
+
+Also, to create a configuration, you can use the keys generated using [dnscrypt-wrapper](https://github.com/cofyc/dnscrypt-wrapper) by running the command:
+
+```
+./dnscrypt convert-dnscrypt-wrapper
+
+[convert-dnscrypt-wrapper command options]
+ -p, --private-key= Path to the DNSCrypt resolver private key file that is used for signing certificates. Param is required.
+ -r, --resolver-secret= Path to the Short-term privacy key file for encrypting/decrypting DNS queries. If not specified, resolver_secret and resolver_public will be randomly generated.
+ -n, --provider-name= DNSCrypt provider name. Param is required.
+ -o, --out= Path to the resulting config file. Param is required.
+ -t, --ttl= Certificate time-to-live (seconds)
+```
+
+
+### Running a server
+
This configuration file can be used to run a DNSCrypt forwarding server:
-```shell script
-./dnscrypt server --config=config.yaml --forward=94.140.14.140:53
+```
+./dnscrypt server
+
+[server command options]
+ -c, --config= Path to the DNSCrypt configuration file. Param is required.
+ -f, --forward= Forwards DNS queries to the specified address (default: 94.140.14.140:53)
+ -l, --listen= Listening addresses (default: 0.0.0.0)
+ -p, --port= Listening ports (default: 443)
```
Now you can go to https://dnscrypt.info/stamps and use `provider_name` and `public_key` from this configuration to generate a DNS stamp. Here's how it looks like for a server running on `127.0.0.1:443`:
@@ -71,26 +104,27 @@ sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T
You can use that stamp to send a DNSCrypt request to your server:
```
-./dnscrypt lookup-stamp \
- --stamp=sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn \
- --domain=example.org \
- --type=a
+./dnscrypt lookup-stamp
+
+[lookup-stamp command options]
+ -n, --network= network type (tcp/udp) (default: udp)
+ -s, --stamp= DNSCrypt resolver stamp. Param is required.
+ -d, --domain= Domain to resolve. Param is required.
+ -t, --type= DNS query type (default: A)
```
You can also send a DNSCrypt request using a command that does not require stamps:
```
./dnscrypt lookup \
- --provider-name=2.dnscrypt-cert.opendns.com \
- --public-key=b7351140206f225d3e2bd822d7fd691ea1c33cc8d6668d0cbe04bfabca43fb79 \
- --addr=208.67.220.220 \
- --domain=example.org \
- --type=a
-```
-In both cases, you can specify the transport using the 'network' flag (`udp` default)
-```
-./dnscrypt lookup-stamp --network={tcp|udp} ...
+[lookup command options]
+ -n, --network= network type (tcp/udp) (default: udp)
+ -p, --provider-name= DNSCrypt resolver provider name. Param is required.
+ -k, --public-key= DNSCrypt resolver public key. Param is required.
+ -a, --addr= Resolver address (IP[:port]). By default, the port is 443. Param is required.
+ -d, --domain= Domain to resolve. Param is required.
+ -t, --type= DNS query type (default: A)
```
## Programming interface
diff --git a/cmd/convert_dnscrypt_wrapper.go b/cmd/convert_dnscrypt_wrapper.go
new file mode 100644
index 0000000..fc889ac
--- /dev/null
+++ b/cmd/convert_dnscrypt_wrapper.go
@@ -0,0 +1,108 @@
+package main
+
+import (
+ "crypto/ed25519"
+ "fmt"
+ "io/ioutil"
+ "time"
+
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/ameshkov/dnscrypt/v2"
+ "golang.org/x/crypto/curve25519"
+ "gopkg.in/yaml.v3"
+)
+
+// ConvertWrapperArgs - "convert-dnscrypt-wrapper" command arguments
+type ConvertWrapperArgs struct {
+ PrivateKeyFile string `short:"p" long:"private-key" description:"Path to the DNSCrypt resolver private key file that is used for signing certificates. Param is required." required:"true"`
+ ResolverSkFile string `short:"r" long:"resolver-secret" description:"Path to the Short-term privacy key file for encrypting/decrypting DNS queries. If not specified, resolver_secret and resolver_public will be randomly generated."`
+ ProviderName string `short:"n" long:"provider-name" description:"DNSCrypt provider name. Param is required." required:"true"`
+ Out string `short:"o" long:"out" description:"Path to the resulting config file. Param is required." required:"true"`
+ CertificateTTL int `short:"t" long:"ttl" description:"Certificate time-to-live (seconds)"`
+}
+
+// convertWrapper - generates DNSCrypt configuration from both dnscrypt and server private keys
+func convertWrapper(args ConvertWrapperArgs) {
+
+ log.Info("Generating configuration for %s", args.ProviderName)
+
+ var rc = dnscrypt.ResolverConfig{
+ EsVersion: dnscrypt.XSalsa20Poly1305,
+ CertificateTTL: time.Duration(args.CertificateTTL) * time.Second,
+ ProviderName: args.ProviderName,
+ }
+
+ // make PrivateKey
+ var privateKey ed25519.PrivateKey
+ privateKey = getFileContent(args.PrivateKeyFile)
+ if len(privateKey) != ed25519.PrivateKeySize {
+ log.Fatal("Invalid private key.")
+ }
+ rc.PrivateKey = dnscrypt.HexEncodeKey(privateKey)
+
+ // make PublicKey
+ publicKey := privateKey.Public().(ed25519.PublicKey)
+ rc.PublicKey = dnscrypt.HexEncodeKey(publicKey)
+
+ // make ResolverSk
+ var resolverSecret ed25519.PrivateKey
+ resolverSecret = getFileContent(args.ResolverSkFile)
+ if len(resolverSecret) != 32 {
+ log.Fatal("Invalid resolver secret key.")
+ }
+ rc.ResolverSk = dnscrypt.HexEncodeKey(resolverSecret)
+
+ // make ResolverPk
+ resolverPublic := getResolverPk(resolverSecret)
+ rc.ResolverPk = dnscrypt.HexEncodeKey(resolverPublic)
+
+ if err := validateRc(rc, publicKey); err != nil {
+ log.Fatalf("Failed to validate resolver config, err: %s", err.Error())
+ }
+
+ out, err := yaml.Marshal(rc)
+ if err != nil {
+ log.Fatalf("Failed to marshall output config, err: %s", err.Error())
+ }
+
+ err = ioutil.WriteFile(args.Out, out, 0600)
+ if err != nil {
+ log.Fatalf("Failed to write file, err: %s", err.Error())
+ }
+}
+
+// validateRc - verifies that the certificate is correctly
+// created and validated for this resolver config. if rc valid returns nil.
+func validateRc(rc dnscrypt.ResolverConfig, publicKey ed25519.PublicKey) error {
+ cert, err := rc.CreateCert()
+ if err != nil {
+ return fmt.Errorf("failed to validate cert, err: %s", err.Error())
+ }
+ if cert == nil {
+ return fmt.Errorf("created cert is empty")
+ }
+ if !cert.VerifyDate() {
+ return fmt.Errorf("cert date is not valid")
+ }
+ if !cert.VerifySignature(publicKey) {
+ return fmt.Errorf("cert signed incorrectly")
+ }
+ return nil
+}
+
+// getResolverPk - calculates public key from private key
+func getResolverPk(private ed25519.PrivateKey) ed25519.PublicKey {
+ resolverSk := [32]byte{}
+ resolverPk := [32]byte{}
+ copy(resolverSk[:], private)
+ curve25519.ScalarBaseMult(&resolverPk, &resolverSk)
+ return resolverPk[:]
+}
+
+func getFileContent(fname string) []byte {
+ bytes, err := ioutil.ReadFile(fname)
+ if err != nil {
+ log.Fatalf("Fail read key file %s, err: %s", fname, err.Error())
+ }
+ return bytes
+}
diff --git a/cmd/generate.go b/cmd/generate.go
index e29c1f6..6d93b4d 100644
--- a/cmd/generate.go
+++ b/cmd/generate.go
@@ -10,10 +10,10 @@ import (
// GenerateArgs - "generate" command arguments
type GenerateArgs struct {
- ProviderName string `short:"p" long:"provider-name" description:"DNSCrypt provider name" required:"true"`
+ ProviderName string `short:"p" long:"provider-name" description:"DNSCrypt provider name. Param is required." required:"true"`
+ Out string `short:"o" long:"out" description:"Path to the resulting config file. Param is required." required:"true"`
PrivateKey string `short:"k" long:"private-key" description:"Private key (hex-encoded)"`
CertificateTTL int `short:"t" long:"ttl" description:"Certificate time-to-live (seconds)"`
- Out string `short:"o" long:"out" description:"Path to the resulting config file" required:"true"`
}
// generate - generates DNSCrypt server configuration
@@ -40,7 +40,7 @@ func generate(args GenerateArgs) {
}
// nolint
- err = ioutil.WriteFile(args.Out, b, 0644)
+ err = ioutil.WriteFile(args.Out, b, 0600)
if err != nil {
log.Fatalf("failed to save %s: %v", args.Out, err)
}
diff --git a/cmd/lookup.go b/cmd/lookup.go
index b6fd956..0e873d4 100644
--- a/cmd/lookup.go
+++ b/cmd/lookup.go
@@ -15,18 +15,18 @@ import (
// LookupStampArgs - "lookup-stamp" command arguments
type LookupStampArgs struct {
Network string `short:"n" long:"network" description:"network type (tcp/udp)" default:"udp"`
- Stamp string `short:"s" long:"stamp" description:"DNSCrypt resolver stamp" required:"true"`
- Domain string `short:"d" long:"domain" description:"Domain to resolve" required:"true"`
+ Stamp string `short:"s" long:"stamp" description:"DNSCrypt resolver stamp. Param is required." required:"true"`
+ Domain string `short:"d" long:"domain" description:"Domain to resolve. Param is required." required:"true"`
Type string `short:"t" long:"type" description:"DNS query type" default:"A"`
}
// LookupArgs - "lookup" command arguments
type LookupArgs struct {
Network string `short:"n" long:"network" description:"network type (tcp/udp)" default:"udp"`
- ProviderName string `short:"p" long:"provider-name" description:"DNSCrypt resolver provider name" required:"true"`
- PublicKey string `short:"k" long:"public-key" description:"DNSCrypt resolver public key" required:"true"`
- ServerAddr string `short:"a" long:"addr" description:"Resolver address (IP[:port]). By default, the port is 443" required:"true"`
- Domain string `short:"d" long:"domain" description:"Domain to resolve" required:"true"`
+ ProviderName string `short:"p" long:"provider-name" description:"DNSCrypt resolver provider name. Param is required." required:"true"`
+ PublicKey string `short:"k" long:"public-key" description:"DNSCrypt resolver public key. Param is required." required:"true"`
+ ServerAddr string `short:"a" long:"addr" description:"Resolver address (IP[:port]). By default, the port is 443. Param is required." required:"true"`
+ Domain string `short:"d" long:"domain" description:"Domain to resolve. Param is required." required:"true"`
Type string `short:"t" long:"type" description:"DNS query type" default:"A"`
}
diff --git a/cmd/main.go b/cmd/main.go
index ce818b5..077b484 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -10,11 +10,12 @@ import (
// Options - command-line options
type Options struct {
- Generate GenerateArgs `command:"generate" description:"Generates DNSCrypt server configuration"`
- LookupStamp LookupStampArgs `command:"lookup-stamp" description:"Performs a DNSCrypt lookup for the specified domain using an sdns:// stamp"`
- Lookup LookupArgs `command:"lookup" description:"Performs a DNSCrypt lookup for the specified domain"`
- Server ServerArgs `command:"server" description:"Runs a DNSCrypt resolver"`
- Version struct {
+ Generate GenerateArgs `command:"generate" description:"Generates DNSCrypt server configuration"`
+ LookupStamp LookupStampArgs `command:"lookup-stamp" description:"Performs a DNSCrypt lookup for the specified domain using an sdns:// stamp"`
+ Lookup LookupArgs `command:"lookup" description:"Performs a DNSCrypt lookup for the specified domain"`
+ Server ServerArgs `command:"server" description:"Runs a DNSCrypt resolver"`
+ ConvertWrapper ConvertWrapperArgs `command:"convert-dnscrypt-wrapper" description:"Converting keys generated with dnscrypt-wrapper to yaml config"`
+ Version struct {
} `command:"version" description:"Prints version"`
}
@@ -45,6 +46,8 @@ func main() {
lookup(opts.Lookup)
case "server":
server(opts.Server)
+ case "convert-dnscrypt-wrapper":
+ convertWrapper(opts.ConvertWrapper)
default:
log.Fatalf("unknown command %s", parser.Active.Name)
}
diff --git a/cmd/server.go b/cmd/server.go
index cb87b72..a10609d 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -15,7 +15,7 @@ import (
// ServerArgs - "server" command arguments
type ServerArgs struct {
- Config string `short:"c" long:"config" description:"Path to the DNSCrypt configuration file" required:"true"`
+ Config string `short:"c" long:"config" description:"Path to the DNSCrypt configuration file. Param is required." required:"true"`
Forward string `short:"f" long:"forward" description:"Forwards DNS queries to the specified address" default:"94.140.14.140:53"`
ListenAddrs []string `short:"l" long:"listen" description:"Listening addresses" default:"0.0.0.0"`
ListenPorts []int `short:"p" long:"port" description:"Listening ports" default:"443"`
diff --git a/generate.go b/generate.go
index 127e4cf..3891e31 100644
--- a/generate.go
+++ b/generate.go
@@ -31,7 +31,7 @@ type ResolverConfig struct {
// If not set, we'll generate a new random ResolverSk and ResolverPk.
ResolverSk string `yaml:"resolver_secret"`
- // ResolverSk - hex-encoded short-term public key corresponding to ResolverSk.
+ // ResolverPk - hex-encoded short-term public key corresponding to ResolverSk.
// This key is used to encrypt/decrypt DNS queries.
ResolverPk string `yaml:"resolver_public"`