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"`