dane

package module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2023 License: MIT Imports: 19 Imported by: 2

README

dane

Go library for DANE TLSA authentication

Pre-requisites
Documentation

Formatted documentation for this module can be found at:

https://pkg.go.dev/github.com/shuque/dane?tab=doc

Description

Package dane provides a set of functions to perform DANE authentication of a TLS server, with fall back to PKIX authentication if no DANE TLSA records exist for the server. DANE is a protocol that employs DNSSEC signed records ("TLSA") to authenticate X.509 certificates used in TLS and other protocols. See RFC 6698 for details.

The dane.Config structure holds all the configured input parameters for DANE authentication, including the server's name, address & port, and the TLSA record set data. A new dane.Config structure has to be instantiated for each DANE TLS server that needs to be authenticated.

The package includes functions that will perform secure lookup of TLSA records and address records via a validating DNS resolver: GetTLSA() and GetAddresses(). Alternatively, if the calling application has obtained the TLSA record data by itself, it can populate the dane.Config's TLSA structure itself.

The use of GetTLSA() and GetAddresses() requires the use of a validating DNS resolver that sets the AD bit on authenticated responses. The GetResolver() function in this package, by default uses the set of resolvers defined in /etc/resolv.conf. This can be overridden by supplying a custom resolv.conf file, or by directly initializing a Resolver structure and placing it in the dane.Config. To be secure, it is important that system the code is running on has a secure connection to the validating resolver. (A future version of this library may perform stub DNSSEC validation itself, in which case it would only need to be able to communicate with a DNSSEC aware resolver, and not require a secure transport connection to it.)

The functions DialTLS() or DialStartTLS() take a dane.Config instance, connect to the server, perform DANE authentication, and return a TLS connection handle for subsequent use. DialStartTLS() will additionally perform an application specific STARTTLS negotiation first. STARTTLS is supported for the SMTP, POP3, IMAP, and XMPP applications by calling the Appname and Servicename methods on the Config structure.

If no secure DANE TLSA records are found, or if the resolver doesn't validate, this package will fallback to normal PKIX authentication. Calling NoPKIXverify() on the Config structure will prevent this and force a requirement for DANE authentication.

Per current spec (RFC 7671, Section 5.1), this library does not perform certificate name checks for DANE-EE mode TLSA records, but this can be overridden with the DaneEEname option. For Web applications it is sensible to set the DaneEEname option to protect against Unknown Keyshare Attacks as described in https://tools.ietf.org/html/draft-barnes-dane-uks-00 .

Also, per RFC 7672, Section 3.1.3, for SMTP STARTTLS the library ignores PKIX-* mode TLSA records, since they are not recommended for use. This can also be overridden by setting the SMTPAnyMode option.

After calling DialTLSA() or DialStartTLSA(), the dane.Config structure is populated with additional diagnostic information, such as DANE and PKIX authentication status, the verified certificate chains, and the verification status of each DANE TLSA record processed.

If dane.Config.DiagMode is set to true, then DialTLSA() and DialStartTLSA() will return a working TLS connection handle even if server authentication fails (rather than an error), but will populate the dane.Config's DiagError member with the appropriate error instead.

Example code

The basic steps in summary form are:

import (
    ...
    "github.com/shuque/dane"
    )

// replace this with the name and port for a valid DANE TLS server
hostname := "www.example.com"
port := 443

resolver, err := dane.GetResolver()
tlsa, err := dane.GetTLSA(resolver, hostname, port)
iplist, err := dane.GetAddresses(resolver, hostname, true)

for _, ip := range iplist {
	daneconfig := dane.NewConfig(hostname, ip, 443)
	daneconfig.SetTLSA(tlsa)
	conn, err := dane.DialTLS(daneconfig)
	if err != nil {
		fmt.Printf("Result: FAILED: %s\n", err.Error())
		continue
	}
	if daneconfig.Okdane {
		fmt.Printf("Result: DANE OK\n")
	} else if daneconfig.Okpkix {
		fmt.Printf("Result: PKIX OK\n")
	} else {
		fmt.Printf("Result: FAILED\n")
	}
	//
	// do some stuff with the obtained TLS connection here
	//
	conn.Close()
}

The ConnectByName(), ConnectByNameAsync(), and ConnectByNameAsync2() functions are simpler all-in-one functions that take a hostname and port argument, and then lookup up TLSA records, connect to the first address associated with the hostname that results in an authenticated connection, and returns the associated TLS connection object.

GetHttpClient() returns a HTTP client structure (net/http.Client) configured to do DANE authentication of a HTTPS server. The "pkixfallback" boolean argument specifies whether or not to fallback to PKIX authentication if there are no secure TLSA records published for the server.

Documentation

Overview

Example
package main

import (
	"fmt"
	"log"

	"github.com/shuque/dane"
)

func main() {

	var daneconfig *dane.Config

	servers := []*dane.Server{dane.NewServer("", "8.8.8.8", 53)}
	resolver := dane.NewResolver(servers)
	hostname := "www.example.com"
	tlsa, err := dane.GetTLSA(resolver, hostname, 443)
	if err != nil {
		log.Fatalf("%s", err)
	}
	if tlsa == nil {
		log.Fatalf("No TLSA records found, where expected.")
	}
	iplist, err := dane.GetAddresses(resolver, hostname, true)
	if err != nil {
		log.Fatalf("%s", err)
	}
	if len(iplist) < 1 {
		log.Fatalf("Got less than expected addresses.")
	}
	for _, ip := range iplist {
		daneconfig = dane.NewConfig(hostname, ip, 443)
		daneconfig.SetTLSA(tlsa)
		conn, err := dane.DialTLS(daneconfig)
		if daneconfig.TLSA != nil {
			daneconfig.TLSA.Results()
		}
		if err != nil {
			fmt.Printf("Result: FAILED: %s\n", err.Error())
			continue
		}
		conn.Close()
		if daneconfig.Okdane {
			fmt.Printf("Result: DANE OK\n")
		} else if daneconfig.Okpkix {
			fmt.Printf("Result: PKIX OK\n")
		} else {
			fmt.Printf("Result: FAILED\n")
		}

	}
}
Output:

Index

Examples

Constants

View Source
const (
	PkixTA = 0 // Certificate Authority Constraint
	PkixEE = 1 // Service Certificate Constraint
	DaneTA = 2 // Trust Anchor Assertion
	DaneEE = 3 // Domain Issued Certificate
)

DANE Certificte Usage modes

Variables

View Source
var IPv6Headstart = 25 * time.Millisecond

IPv6 connect headstart (delay IPv4 connections by this amount)

View Source
var MaxParallelConnections = 30

Maximum number of parallel connections attempted

View Source
var Version = VersionStruct{0, 2, 3}

Version - current version number

Functions

func AuthenticateAll

func AuthenticateAll(daneconfig *Config)

AuthenticateAll performs DANE authentication of a set of certificate chains. The TLSA RRset information is expected to be pre-initialized in the dane Config structure.

func AuthenticateSingle

func AuthenticateSingle(chain []*x509.Certificate, tr *TLSArdata, daneconfig *Config) bool

AuthenticateSingle performs DANE authentication of a single certificate chain, using a single TLSA resource data. Returns true or false accordingly.

func CertToPEMBytes added in v0.2.0

func CertToPEMBytes(cert *x509.Certificate) []byte

CertToPEMBytes returns PEM encoded bytes corresponding to the given x.509 certificate.

func ChainMatchesTLSA

func ChainMatchesTLSA(chain []*x509.Certificate, tr *TLSArdata, daneconfig *Config) bool

ChainMatchesTLSA checks that the TLSA record data (tr) has a corresponding match in the certificate chain (chain). Only one TLSA record needs to match for the chain to be considered matched. However, this function checks all available TLSA records and records the results of the match in the TLSArdata structure. These results can be useful to diagnostic tools using this package.

func ComputeTLSA

func ComputeTLSA(selector, mtype uint8, cert *x509.Certificate) (string, error)

ComputeTLSA calculates the TLSA rdata hash value for the given certificate from the given DANE selector and matching type. Returns the hex encoded string form of the value, and sets error to non-nil on failure.

func DialStartTLS

func DialStartTLS(daneconfig *Config) (*tls.Conn, error)

DialStartTLS takes a pointer to an initialized dane Config structure, connects to the defined server, speaks the necessary application protocol preamble to activate STARTTLS, then negotiates TLS and returns the TLS connection. The error return parameter is nil on success, and appropriately populated if not.

DialStartTLS obtains a TLS config structure, initialized with Dane verification callbacks, and connects to the server network address defined in Config using tls.DialWithDialer().

func DialTLS

func DialTLS(daneconfig *Config) (*tls.Conn, error)

DialTLS takes a pointer to an initialized dane Config structure, establishes and returns a TLS connection. The error return parameter is nil on success, and appropriately populated if not.

DialTLS obtains a TLS config structure initialized with Dane verification callbacks, and connects to the server network address defined in Config using tls.DialWithDialer().

func DoIMAP

func DoIMAP(tlsconfig *tls.Config, daneconfig *Config) (*tls.Conn, error)

DoIMAP connects to an IMAP server, issues a STARTTLS command, negotiates TLS, and returns a TLS connection.

func DoPOP3

func DoPOP3(tlsconfig *tls.Config, daneconfig *Config) (*tls.Conn, error)

DoPOP3 connects to a POP3 server, sends the STLS command, negotiates TLS, and returns a TLS connection.

func DoSMTP

func DoSMTP(tlsconfig *tls.Config, daneconfig *Config) (*tls.Conn, error)

DoSMTP connects to an SMTP server, checks for STARTTLS support, negotiates TLS, and returns a TLS connection.

func DoXMPP

func DoXMPP(tlsconfig *tls.Config, daneconfig *Config) (*tls.Conn, error)

DoXMPP connects to an XNPP server, issue a STARTTLS command, negotiates TLS and returns a TLS connection. See RFC 6120, Section 5.4.2 for details.

func GetAddresses

func GetAddresses(resolver *Resolver, hostname string, secure bool) ([]net.IP, error)

GetAddresses obtains a list of IPv4 and IPv6 addresses for given hostname.

func GetHttpClient added in v0.1.12

func GetHttpClient(pkixfallback bool) http.Client

GetHttpClient returns a net/http Client structure configured to perform DANE TLS authentication of the HTTPS server. If the argument pkixfallback is set to true, then PKIX authentication will be attempted if the server does not have any published secure DANE TLSA records.

func GetTLSconfig

func GetTLSconfig(daneconfig *Config) *tls.Config

GetTLSconfig takes a dane Config structure, and returns a tls Config initialized with the ServerName, other specified TLS parameters, and a custom server certificate verification callback that performs DANE authentication.

func StartTLS

func StartTLS(tlsconfig *tls.Config, daneconfig *Config) (*tls.Conn, error)

StartTLS -

func TLShandshake

func TLShandshake(conn net.Conn, config *tls.Config) (*tls.Conn, error)

TLShandshake takes a network connection and a TLS Config structure, negotiates TLS on the connection and returns a TLS connection on success. It sets error to non-nil on failure.

Types

type Config

type Config struct {
	DiagMode    bool                  // Diagnostic mode
	DiagError   error                 // Holds possible error in Diagnostic mode
	Server      *Server               // Server structure (name, ip, port)
	TimeoutTCP  int                   // TCP timeout in seconds
	NoVerify    bool                  // Don't verify server certificate
	TLSversion  uint16                // TLS version number (otherwise use best TLS version offered)
	PKIXRootCA  []byte                // Use PEM bytes as Root CA store for PKIX authentication
	ALPN        []string              // ALPN strings to send
	DaneEEname  bool                  // Do name checks even for DANE-EE mode
	SMTPAnyMode bool                  // Allow any DANE modes for SMTP
	Appname     string                // STARTTLS application name
	Servicename string                // Servicename, if different from server
	Transcript  string                // StartTLS transcript
	DANE        bool                  // do DANE authentication
	PKIX        bool                  // fall back to PKIX authentication
	Okdane      bool                  // DANE authentication result
	Okpkix      bool                  // PKIX authentication result
	TLSA        *TLSAinfo             // TLSA RRset information
	PeerChain   []*x509.Certificate   // Peer Certificate Chain
	PKIXChains  [][]*x509.Certificate // PKIX Certificate Chains
	DANEChains  [][]*x509.Certificate // DANE Certificate Chains
}

Config contains a DANE configuration for a single Server.

func ConnectByName added in v0.1.2

func ConnectByName(hostname string, port int) (*tls.Conn, *Config, error)

ConnectByName takes a hostname and port, resolves the addresses for the hostname (IPv6 followed by IPv4), and then attempts to connect to them and establish TLS using DANE or PKIX authentication - DANE is attempted if there are secure TLSA records, otherwise it falls back to PKIX authentication. It returns a TLS connection and dane config for the first address that succeeds.

Uses a default DANE configuration. For a custom DANE configuration, use the DialTLS or DialStartTLS functions instead.

func ConnectByNameAsync added in v0.1.11

func ConnectByNameAsync(hostname string, port int) (*tls.Conn, *Config, error)

ConnectByNameAsync is an async version of ConnectByName that tries to connect to all server addresses in parallel, and returns the first successful connection. IPv4 connections are intentionally delayed by an IPv6HeadStart amount of time. Performs DANE authentication with fallback to PKIX if no secure TLSA records are found.

func ConnectByNameAsync2 added in v0.1.12

func ConnectByNameAsync2(hostname string, port int, pkixfallback bool) (*tls.Conn, *Config, error)

ConnectByNameAsync2 is the same as ConnectByNameAsync, but supports an additional argument to specify whether PKIX fallback should be performed. By setting that argument to false, we can require DANE only authentication.

func ConnectByNameAsyncBase added in v0.1.12

func ConnectByNameAsyncBase(hostname string, port int, pkixfallback bool) (*tls.Conn, *Config, error)

ConnectByNameAsyncBase. Should not be called directly. Instead call either ConnectByNameAsync or ConnectByNameAsync2

func NewConfig

func NewConfig(hostname string, ip interface{}, port int) *Config

NewConfig initializes and returns a new dane Config structure for the given server name, ip address and port. The IP address can be specified either as a string or a net.IP structure. The initialized config does DANE authentication with fallback to PKIX.

func (*Config) NoPKIXfallback

func (c *Config) NoPKIXfallback()

NoPKIXfallback sets Config to not allow PKIX fallback. Only DANE authentication is permitted.

func (*Config) SetALPN added in v0.1.10

func (c *Config) SetALPN(alpnStrings []string)

SetALPN sets ALPN strings to be used.

func (*Config) SetAppName

func (c *Config) SetAppName(appname string)

SetAppName sets the STARTTLS application name.

func (*Config) SetDiagMode added in v0.1.9

func (c *Config) SetDiagMode(value bool)

SetDiagMode sets the Diagnostic mode.

func (*Config) SetServer

func (c *Config) SetServer(server *Server)

SetServer set the Server component of Config.

func (*Config) SetServiceName

func (c *Config) SetServiceName(servicename string)

SetServiceName sets the STARTTLS service name.

func (*Config) SetTLSA

func (c *Config) SetTLSA(tlsa *TLSAinfo)

SetTLSA sets the TLSAinfo component of Config. A copy of the TLSAinfo structure is made, to permit concurrent use of the structure that may independently change the (reset) checking bits.

type Query

type Query struct {
	Name  string
	Type  uint16
	Class uint16
}

Query contains parameters of a DNS query: name, type, and class.

func NewQuery

func NewQuery(qname string, qtype uint16, qclass uint16) *Query

NewQuery returns an initialized Query structure from the given query parameters.

type Resolver

type Resolver struct {
	Servers      []*Server     // list of resolvers
	Rdflag       bool          // set RD flag
	Adflag       bool          // set AD flag
	Cdflag       bool          // set CD flag
	Timeout      time.Duration // query timeout
	Retries      int           // query retries
	Payload      uint16        // EDNS0 UDP payload size
	IPv6         bool          // lookup AAAA records in getAddresses()
	IPv4         bool          // look A records in getAddresses()
	Pkixfallback bool          // whether to fallback to PKIX in getTLSA()
}

Resolver contains a DNS resolver configuration

func GetResolver

func GetResolver(resconf string) (*Resolver, error)

GetResolver returns a Resolver configuration structure containing a list of DNS resolver addresses obtained from a custom resolver configuration file or from the system default (/etc/resolv.conf) if the config file is unspecified.

func NewResolver

func NewResolver(servers []*Server) *Resolver

NewResolver initializes a new Resolver structure from a given IP address (net.IP) and port number.

type Response added in v0.1.11

type Response struct {
	// contains filtered or unexported fields
}

Response - response information

type Server

type Server struct {
	Name   string
	Ipaddr net.IP
	Port   int
}

Server contains information about a single server: hostname, IP address (net.IP) and port number.

func NewServer

func NewServer(name string, ip interface{}, port int) *Server

NewServer returns an initialized Server structure from given name, IP address, and port.

func (*Server) Address

func (s *Server) Address() string

Address returns an address string for the Server.

func (*Server) String

func (s *Server) String() string

String returns a string representation of Server.

type TLSAinfo

type TLSAinfo struct {
	Qname string
	Alias []string
	Rdata []*TLSArdata
}

TLSAinfo contains details of the TLSA RRset.

func GetTLSA

func GetTLSA(resolver *Resolver, hostname string, port int) (*TLSAinfo, error)

GetTLSA returns the DNS TLSA RRset information for the given hostname, port and resolver parameters.

func Message2TSLAinfo added in v0.1.4

func Message2TSLAinfo(qname string, message *dns.Msg) *TLSAinfo

Message2TSLAinfo returns a populated TLSAinfo structure from the contents of a given dns message that contains a response to a TLSA query. The qname parameter provides the expected TLSA query name string.

func (*TLSAinfo) Copy added in v0.1.5

func (t *TLSAinfo) Copy() *TLSAinfo

Copy makes a deep copy of the TLSAinfo structure

func (*TLSAinfo) Print

func (t *TLSAinfo) Print()

Print prints information about the TLSAinfo TLSA RRset.

func (*TLSAinfo) Results

func (t *TLSAinfo) Results()

Results prints TLSA RRset certificate matching results.

func (*TLSAinfo) ResultsString added in v0.2.3

func (t *TLSAinfo) ResultsString() string

ResultsString is like Results but returns a string.

func (*TLSAinfo) Uncheck

func (t *TLSAinfo) Uncheck()

Uncheck unchecks result fields of all the TLSA rdata structs.

type TLSArdata

type TLSArdata struct {
	Usage    uint8  // Certificate Usage
	Selector uint8  // Selector: 0: full cert, 1: subject public key
	Mtype    uint8  // Matching Type: 0: full content, 1: SHA256, 2: SHA512
	Data     string // Certificate association Data field (hex encoding)
	Checked  bool   // Have we tried to match this TLSA rdata?
	Ok       bool   // Did it match?
	Message  string // Diagnostic message for matching
}

TLSArdata - TLSA rdata structure

func (*TLSArdata) String

func (tr *TLSArdata) String() string

String returns a string representation of the TLSA rdata.

type VersionStruct

type VersionStruct struct {
	Major, Minor, Patch int
}

VersionStruct - version structure

func (VersionStruct) String

func (v VersionStruct) String() string

String representation of version

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL