From 3093b490932114557c1b8dd359d15717fc7dffc9 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Thu, 4 Aug 2022 13:36:36 +0200 Subject: [PATCH] Adding openssl prototype via cgo bindings (#64) * Adding openssl prototype via cgo bindings * making openssl optional * using zcrypto * removing unused fields * attempting to fix go.sum hashes * moving code around + fixing connect issues * Added openssl support to auto mode + misc * Added SNI support to openssl + misc fixes to auto * Fixed errrors in logic + remove dead code * Accept any version of SSL * adding linux snippet + updating readme * listing openssl only if supported * fixing go.mod Co-authored-by: Ice3man --- README.md | 24 +++++ assets/openssl.include | 13 +++ cmd/tlsx/main.go | 16 ++- go.mod | 4 +- go.sum | 7 +- internal/runner/runner.go | 1 + pkg/output/stats/stats.go | 13 +++ pkg/tlsx/auto/auto.go | 37 +++---- pkg/tlsx/clients/clients.go | 26 +++++ pkg/tlsx/openssl/common.go | 5 + pkg/tlsx/openssl/no_openssl.go | 24 +++++ pkg/tlsx/openssl/openssl.go | 188 +++++++++++++++++++++++++++++++++ pkg/tlsx/tls/tls.go | 31 +----- pkg/tlsx/tlsx.go | 3 + pkg/tlsx/ztls/ztls.go | 12 ++- 15 files changed, 345 insertions(+), 59 deletions(-) create mode 100644 assets/openssl.include create mode 100644 pkg/tlsx/openssl/common.go create mode 100644 pkg/tlsx/openssl/no_openssl.go create mode 100644 pkg/tlsx/openssl/openssl.go diff --git a/README.md b/README.md index dc3ec7d7..3de26d4b 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,30 @@ $ tlsx -u example.com -ci TLS_AES_256_GCM_SHA384 -cipher $ tlsx -u example.com -ci cipher_list.txt -cipher ``` +### OpenSSL +`tlsx` can be built with support for `OpenSSL` for osx and linux systems. The library must be installed with the following commands: + +- OSx: `brew install openssl` +- Linux: `apt install openssl` + +On some linux systems the default configuration is restrictive, and in order to allow more tls coverage the enclosed `assets/openssl.include` should be copied onto the system and the following snippet added to `/etc/ssl/openssl.cnf`: + +``` +.include /path/to/openssl.include +``` + +Finally the binary must be built with the `openssl` tag: + +```console +go build -tags openssl . +``` + +At this point the engine can be used with: + +```console +tlsx -sm openssl -json +``` + ## Acknowledgements This program optionally uses the [zcrypto](https://github.com/zmap/zcrypto) library from the zmap team. diff --git a/assets/openssl.include b/assets/openssl.include new file mode 100644 index 00000000..a0428bf0 --- /dev/null +++ b/assets/openssl.include @@ -0,0 +1,13 @@ +openssl_conf = default_conf + +[ default_conf ] + +ssl_conf = ssl_sect + +[ssl_sect] + +system_default = system_default_sect + +[system_default_sect] +MinProtocol = TLSv1 +CipherString = DEFAULT:@SECLEVEL=1 \ No newline at end of file diff --git a/cmd/tlsx/main.go b/cmd/tlsx/main.go index 3a9f1b3c..6a42dc3f 100644 --- a/cmd/tlsx/main.go +++ b/cmd/tlsx/main.go @@ -1,11 +1,14 @@ package main import ( + "strings" + "github.com/pkg/errors" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/tlsx/internal/runner" "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" + "github.com/projectdiscovery/tlsx/pkg/tlsx/openssl" ) var ( @@ -14,6 +17,7 @@ var ( ) func main() { + if err := process(); err != nil { gologger.Fatal().Msgf("Could not process: %s", err) } @@ -44,16 +48,22 @@ func readFlags() error { flagSet.SetDescription(`TLSX is a tls data gathering and analysis toolkit.`) flagSet.CreateGroup("input", "Input", - flagSet.StringSliceVarP(&options.Inputs, "host", "u", []string{}, "target host to scan (-u INPUT1,INPUT2)", goflags.CommaSeparatedStringSliceOptions), + flagSet.StringSliceVarP(&options.Inputs, "host", "u", nil, "target host to scan (-u INPUT1,INPUT2)", goflags.CommaSeparatedStringSliceOptions), flagSet.StringVarP(&options.InputList, "list", "l", "", "target list to scan (-l INPUT_FILE)"), flagSet.StringSliceVarP(&options.Ports, "port", "p", nil, "target port to connect (default 443)", goflags.FileCommaSeparatedStringSliceOptions), ) + availableScanModes := []string{"ctls", "ztls"} + if openssl.Enabled { + availableScanModes = append(availableScanModes, "openssl") + } + availableScanModes = append(availableScanModes, "auto") + flagSet.CreateGroup("scan-mode", "Scan-Mode", - flagSet.StringVarP(&options.ScanMode, "scan-mode", "sm", "", "tls connection mode to use (ctls, ztls, auto) (default ctls)"), + flagSet.StringVarP(&options.ScanMode, "scan-mode", "sm", "", "tls connection mode to use ("+strings.Join(availableScanModes, ", ")+") (default ctls)"), flagSet.BoolVarP(&options.CertsOnly, "pre-handshake", "ps", false, "enable pre-handshake tls connection (early termination) using ztls"), flagSet.BoolVarP(&options.ScanAllIPs, "scan-all-ips", "sa", false, "scan all ips for a host (default false)"), - flagSet.StringSliceVarP(&options.IPVersion, "ip-version", "iv", []string{}, "ip version to use (4, 6) (default 4)", goflags.NormalizedStringSliceOptions), + flagSet.StringSliceVarP(&options.IPVersion, "ip-version", "iv", nil, "ip version to use (4, 6) (default 4)", goflags.NormalizedStringSliceOptions), ) flagSet.CreateGroup("probes", "Probes", diff --git a/go.mod b/go.mod index 36116015..ab6184dc 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/projectdiscovery/sliceutil v0.0.0-20220617151003-15892688e1d6 github.com/projectdiscovery/stringsutil v0.0.0-20220612082425-0037ce9f89f3 github.com/rs/xid v1.4.0 + github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c go.uber.org/multierr v1.8.0 ) @@ -53,12 +54,13 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/projectdiscovery/blackrock v0.0.0-20210903102120-5a9d2412d21d // indirect github.com/projectdiscovery/cryptoutil v0.0.0-20210805184155-b5d2512f9345 // indirect - github.com/projectdiscovery/hmap v0.0.2-0.20220308104318-929a45d8e775 // indirect + github.com/projectdiscovery/hmap v0.0.2 // indirect github.com/projectdiscovery/networkpolicy v0.0.1 // indirect github.com/projectdiscovery/retryabledns v1.0.13 // indirect github.com/projectdiscovery/retryablehttp-go v1.0.2 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect + github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 // indirect github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236 // indirect diff --git a/go.sum b/go.sum index e1659cb5..51e4907b 100644 --- a/go.sum +++ b/go.sum @@ -269,8 +269,9 @@ github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTt github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY= github.com/projectdiscovery/hmap v0.0.1/go.mod h1:VDEfgzkKQdq7iGTKz8Ooul0NuYHQ8qiDs6r8bPD1Sb0= github.com/projectdiscovery/hmap v0.0.2-0.20210917080408-0fd7bd286bfa/go.mod h1:lV5f/PNPmCCjCN/dR317/chN9s7VG5h/xcbFfXOz8Fo= -github.com/projectdiscovery/hmap v0.0.2-0.20220308104318-929a45d8e775 h1:6E9T0I+6TfQYaYxEwiNKIIhXasz/tl1pLbt8gIEVXqw= github.com/projectdiscovery/hmap v0.0.2-0.20220308104318-929a45d8e775/go.mod h1:RWJgRqpBPJW1R9CzcEH3zmiqPFEOfcbn6lLvHD9xrKE= +github.com/projectdiscovery/hmap v0.0.2 h1:fe3k0b6tj95mn9a1phD3JXvAAOmmWAh/Upg1Bf0Tfos= +github.com/projectdiscovery/hmap v0.0.2/go.mod h1:YU3TeNTDmLW2dtb4LvuEtDsPTsQ06XMnmOeD3KOuU6c= github.com/projectdiscovery/ipranger v0.0.2/go.mod h1:kcAIk/lo5rW+IzUrFkeYyXnFJ+dKwYooEOHGVPP/RWE= github.com/projectdiscovery/iputil v0.0.0-20210414194613-4b4d2517acf0/go.mod h1:PQAqn5h5NXsQTF4ZA00ZTYLRzGCjOtcCq8llAqrsd1A= github.com/projectdiscovery/iputil v0.0.0-20210804143329-3a30fcde43f3/go.mod h1:blmYJkS8lSrrx3QcmcgS2tZIxlojeVmoGeA9twslCBU= @@ -317,6 +318,10 @@ github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a h1:/eS3yfGjQKG+9kayBkj0ip1BGhq6zJ3eaVksphxAaek= +github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 5e4c9875..e6ee3f0a 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -129,6 +129,7 @@ func (r *Runner) Execute() error { if r.options.ScanMode == "auto" { gologger.Info().Msgf("Connections made using crypto/tls: %d", stats.LoadCryptoTLSConnections()) gologger.Info().Msgf("Connections made using zcrypto/tls: %d", stats.LoadZcryptoTLSConnections()) + gologger.Info().Msgf("Connections made using openssl: %d", stats.LoadOpensslTLSConnections()) } return nil } diff --git a/pkg/output/stats/stats.go b/pkg/output/stats/stats.go index 817d13a9..e2b7d1f5 100644 --- a/pkg/output/stats/stats.go +++ b/pkg/output/stats/stats.go @@ -9,6 +9,9 @@ var ( // zCryptoTLSConnections contains number of connections made with // zcrypto/tls zcryptoTLSConnections uint64 + // opensslTLSConnections contains number of connections made with + // openssl + opensslTLSConnections uint64 ) // IncrementCryptoTLSConnections increments crypto/tls connections @@ -21,6 +24,11 @@ func IncrementZcryptoTLSConnections() { atomic.AddUint64(&zcryptoTLSConnections, 1) } +// IncrementOpensslTLSConnections increments openssl connections +func IncrementOpensslTLSConnections() { + atomic.AddUint64(&opensslTLSConnections, 1) +} + // LoadCryptoTLSConnections returns crypto/tls connections func LoadCryptoTLSConnections() uint64 { return atomic.LoadUint64(&cryptoTLSConnections) @@ -30,3 +38,8 @@ func LoadCryptoTLSConnections() uint64 { func LoadZcryptoTLSConnections() uint64 { return atomic.LoadUint64(&zcryptoTLSConnections) } + +// LoadOpensslTLSConnections returns openssl connections +func LoadOpensslTLSConnections() uint64 { + return atomic.LoadUint64(&opensslTLSConnections) +} diff --git a/pkg/tlsx/auto/auto.go b/pkg/tlsx/auto/auto.go index f869c69a..fa5a46b8 100644 --- a/pkg/tlsx/auto/auto.go +++ b/pkg/tlsx/auto/auto.go @@ -3,19 +3,19 @@ package auto import ( - "strings" - "github.com/pkg/errors" "github.com/projectdiscovery/tlsx/pkg/output/stats" "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" + "github.com/projectdiscovery/tlsx/pkg/tlsx/openssl" "github.com/projectdiscovery/tlsx/pkg/tlsx/tls" "github.com/projectdiscovery/tlsx/pkg/tlsx/ztls" ) // Client is a TLS grabbing client using auto fallback type Client struct { - tlsClient *tls.Client - ztlsClient *ztls.Client + tlsClient *tls.Client + ztlsClient *ztls.Client + opensslClient *openssl.Client } // New creates a new grabbing client using auto fallback @@ -28,17 +28,26 @@ func New(options *clients.Options) (*Client, error) { if err != nil { return nil, errors.Wrap(err, "could not create ztls client") } - return &Client{tlsClient: tlsClient, ztlsClient: ztlsClient}, nil + opensslClient, err := openssl.New(options) + if err != nil && err != openssl.ErrNotSupported { + return nil, errors.Wrap(err, "could not create ztls client") + } + return &Client{tlsClient: tlsClient, ztlsClient: ztlsClient, opensslClient: opensslClient}, nil } // Connect connects to a host and grabs the response data func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.ConnectOptions) (*clients.Response, error) { response, err := c.tlsClient.ConnectWithOptions(hostname, ip, port, options) - isInvalidResponse := c.isResponseInvalid(response) - if err != nil || isInvalidResponse { + if err != nil { ztlsResponse, ztlsErr := c.ztlsClient.ConnectWithOptions(hostname, ip, port, options) if ztlsErr != nil { - return nil, ztlsErr + opensslResponse, opensslError := c.opensslClient.ConnectWithOptions(hostname, ip, port, options) + if opensslError != nil { + return nil, opensslError + } + opensslResponse.TLSConnection = "openssl" + stats.IncrementOpensslTLSConnections() + return opensslResponse, nil } ztlsResponse.TLSConnection = "ztls" stats.IncrementZcryptoTLSConnections() @@ -48,15 +57,3 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C stats.IncrementCryptoTLSConnections() return response, nil } - -// isResponseInvalid handles invalid response -func (c *Client) isResponseInvalid(resp *clients.Response) bool { - if resp == nil { - return true - } - // case for invalid google resolving response - if strings.EqualFold(resp.CertificateResponse.IssuerCN, "invalid2.invalid") { - return true - } - return false -} diff --git a/pkg/tlsx/clients/clients.go b/pkg/tlsx/clients/clients.go index 3af11094..73cfec74 100644 --- a/pkg/tlsx/clients/clients.go +++ b/pkg/tlsx/clients/clients.go @@ -14,6 +14,8 @@ import ( "github.com/projectdiscovery/fastdialer/fastdialer" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/stringsutil" + zasn1 "github.com/zmap/zcrypto/encoding/asn1" + zpkix "github.com/zmap/zcrypto/x509/pkix" ) // Implementation is an interface implemented by TLSX client @@ -289,3 +291,27 @@ func PemEncode(cert []byte) string { type ConnectOptions struct { SNI string } + +// ParseASN1DNSequenceWithZpkixOrDefault return the parsed value of ASN1DNSequence or a default string value +func ParseASN1DNSequenceWithZpkixOrDefault(data []byte, defaultValue string) string { + if value := ParseASN1DNSequenceWithZpkix(data); value != "" { + return value + } + return defaultValue +} + +// ParseASN1DNSequenceWithZpkix tries to parse raw ASN1 of a TLS DN with zpkix and +// zasn1 library which includes additional information not parsed by go standard +// library which may be useful. +// +// If the parsing fails, a blank string is returned and the standard library data is used. +func ParseASN1DNSequenceWithZpkix(data []byte) string { + var rdnSequence zpkix.RDNSequence + var name zpkix.Name + if _, err := zasn1.Unmarshal(data, &rdnSequence); err != nil { + return "" + } + name.FillFromRDNSequence(&rdnSequence) + dnParsedString := name.String() + return dnParsedString +} diff --git a/pkg/tlsx/openssl/common.go b/pkg/tlsx/openssl/common.go new file mode 100644 index 00000000..048bad8e --- /dev/null +++ b/pkg/tlsx/openssl/common.go @@ -0,0 +1,5 @@ +package openssl + +import "github.com/pkg/errors" + +var ErrNotSupported = errors.New("openssl not supported") diff --git a/pkg/tlsx/openssl/no_openssl.go b/pkg/tlsx/openssl/no_openssl.go new file mode 100644 index 00000000..ff0c4085 --- /dev/null +++ b/pkg/tlsx/openssl/no_openssl.go @@ -0,0 +1,24 @@ +//go:build !openssl + +// Package openssl implements a tls grabbing implementation using openssl +package openssl + +import ( + "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" +) + +// Enabled reports if the tool was compiled with openssl support +const Enabled = false + +// Client is a TLS grabbing client using crypto/tls +type Client struct{} + +// New creates a new grabbing client using crypto/tls +func New(options *clients.Options) (*Client, error) { + return nil, ErrNotSupported +} + +// Connect connects to a host and grabs the response data +func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.ConnectOptions) (*clients.Response, error) { + return nil, ErrNotSupported +} diff --git a/pkg/tlsx/openssl/openssl.go b/pkg/tlsx/openssl/openssl.go new file mode 100644 index 00000000..80d1d464 --- /dev/null +++ b/pkg/tlsx/openssl/openssl.go @@ -0,0 +1,188 @@ +//go:build (linux || darwin) && openssl + +// Package openssl implements a tls grabbing implementation using openssl +package openssl + +import ( + "context" + "encoding/pem" + "io/ioutil" + "net" + "strings" + "time" + + "github.com/rs/xid" + "github.com/zmap/zcrypto/x509" + + "github.com/pkg/errors" + + "github.com/projectdiscovery/fastdialer/fastdialer" + "github.com/projectdiscovery/iputil" + "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" + "github.com/projectdiscovery/tlsx/pkg/tlsx/ztls" + "github.com/spacemonkeygo/openssl" +) + +// Enabled reports if the tool was compiled with openssl support +const Enabled = true + +// Client is a TLS grabbing client using crypto/tls +type Client struct { + dialer *fastdialer.Dialer + openSSLDialFlags []openssl.DialFlags + options *clients.Options +} + +// New creates a new grabbing client using crypto/tls +func New(options *clients.Options) (*Client, error) { + c := &Client{ + dialer: options.Fastdialer, + options: options, + } + return c, nil +} + +// Connect connects to a host and grabs the response data +func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.ConnectOptions) (*clients.Response, error) { + address := net.JoinHostPort(hostname, port) + + if c.options.ScanAllIPs || len(c.options.IPVersion) > 0 { + address = net.JoinHostPort(ip, port) + } + + opensslCtx, err := openssl.NewCtxWithVersion(openssl.AnyVersion) + if err != nil { + return nil, err + } + opensslCtx.SetVerifyMode(openssl.VerifyNone) + + if c.options.Timeout > 0 { + opensslCtx.SetTimeout(time.Duration(c.options.Timeout) * time.Second) + } + + if len(c.options.Ciphers) > 0 { + if err := opensslCtx.SetCipherList(strings.Join(c.options.Ciphers, ",")); err != nil { + return nil, errors.Wrap(err, "could not set ciphers") + } + } + + if c.options.CACertificate != "" { + caCert, err := ioutil.ReadFile(c.options.CACertificate) + if err != nil { + return nil, errors.Wrap(err, "could not read ca certificate") + } + caStore := opensslCtx.GetCertificateStore() + err = caStore.LoadCertificatesFromPEM(caCert) + if err != nil { + return nil, errors.Wrap(err, "could not add certificate to store") + } + } + + ctx := context.Background() + if c.options.Timeout != 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, time.Duration(c.options.Timeout)*time.Second) + defer cancel() + } + + rawConn, err := c.dialer.Dial(ctx, "tcp", address) + if err != nil { + return nil, errors.Wrap(err, "could not dial address") + } + defer rawConn.Close() + + var resolvedIP string + if !iputil.IsIP(hostname) { + resolvedIP = c.dialer.GetDialedIP(hostname) + if resolvedIP == "" { + resolvedIP = ip + } + } + + conn, err := openssl.Client(rawConn, opensslCtx) + if err != nil { + return nil, errors.Wrap(err, "could not wrap raw conn") + } + defer conn.Close() + + if options.SNI != "" { + err = conn.SetTlsExtHostName(options.SNI) + } else if iputil.IsIP(hostname) { + // using a random sni will return the default server certificate + err = conn.SetTlsExtHostName(xid.New().String()) + } else { + err = conn.SetTlsExtHostName(hostname) + } + if err != nil { + return nil, errors.New("could not set custom SNI") + } + + // ignoring handshake errors + _ = conn.Handshake() + + peerCertificates, err := conn.PeerCertificateChain() + if err != nil { + return nil, errors.Wrap(err, "could not get peer certificates") + } + + if len(peerCertificates) == 0 { + return nil, errors.New("no certificates returned by server") + } + + tlsCipher, err := conn.CurrentCipher() + if err != nil { + return nil, errors.Wrap(err, "could not get current cipher") + } + + leafCertificate := peerCertificates[0] + certificateChain := peerCertificates[1:] + serverName := conn.GetServername() + + x509LeafCertificate, err := c.convertOpenSSLToX509Certificate(leafCertificate) + if err != nil { + return nil, errors.Wrap(err, "could not convert openssl leaf certificate") + } + + now := time.Now() + response := &clients.Response{ + Timestamp: &now, + Host: hostname, + IP: resolvedIP, + ProbeStatus: true, + Port: port, + Cipher: tlsCipher, + TLSConnection: "openssl", + CertificateResponse: ztls.ConvertCertificateToResponse(c.options, hostname, x509LeafCertificate), + ServerName: serverName, + } + if c.options.TLSChain { + for _, opensslCert := range certificateChain { + x509Cert, err := c.convertOpenSSLToX509Certificate(opensslCert) + if err != nil { + return nil, errors.Wrap(err, "could not convert openssl chain certificate") + } + response.Chain = append(response.Chain, ztls.ConvertCertificateToResponse(c.options, hostname, x509Cert)) + } + } + return response, nil +} + +func (c *Client) convertOpenSSLToX509Certificate(opensslCert *openssl.Certificate) (*x509.Certificate, error) { + pemBytes, err := opensslCert.MarshalPEM() + if err != nil { + return nil, errors.Wrap(err, "could not marshal openssl to pem x509") + } + pemBlock, _ := pem.Decode(pemBytes) + if err != nil { + return nil, errors.Wrap(err, "could not read openssl pem x509 to go pem") + } + if pemBlock.Type != "CERTIFICATE" { + return nil, errors.Wrap(err, "unsupported pem block type") + } + x509Certificate, err := x509.ParseCertificate(pemBlock.Bytes) + if err != nil { + return nil, errors.Wrap(err, "could not convert openssl x509 to go x509") + } + + return x509Certificate, nil +} diff --git a/pkg/tlsx/tls/tls.go b/pkg/tlsx/tls/tls.go index 2c86a5d5..5d0fda1b 100644 --- a/pkg/tlsx/tls/tls.go +++ b/pkg/tlsx/tls/tls.go @@ -17,9 +17,6 @@ import ( "github.com/projectdiscovery/iputil" "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" "github.com/rs/xid" - - zasn1 "github.com/zmap/zcrypto/encoding/asn1" - zpkix "github.com/zmap/zcrypto/x509/pkix" ) // Client is a TLS grabbing client using crypto/tls @@ -197,34 +194,10 @@ func (c *Client) convertCertificateToResponse(hostname string, cert *x509.Certif SHA256: clients.SHA256Fingerprint(cert.Raw), }, } - if parsedIssuer := parseASN1DNSequenceWithZpkix(cert.RawIssuer); parsedIssuer != "" { - response.IssuerDN = parsedIssuer - } else { - response.IssuerDN = cert.Issuer.String() - } - if parsedSubject := parseASN1DNSequenceWithZpkix(cert.RawSubject); parsedSubject != "" { - response.SubjectDN = parsedSubject - } else { - response.SubjectDN = cert.Subject.String() - } + response.IssuerDN = clients.ParseASN1DNSequenceWithZpkixOrDefault(cert.RawIssuer, cert.Issuer.String()) + response.SubjectDN = clients.ParseASN1DNSequenceWithZpkixOrDefault(cert.RawSubject, cert.Subject.String()) if c.options.Cert { response.Certificate = clients.PemEncode(cert.Raw) } return response } - -// parseASN1DNSequenceWithZpkix tries to parse raw ASN1 of a TLS DN with zpkix and -// zasn1 library which includes additional information not parsed by go standard -// library which may be useful. -// -// If the parsing fails, a blank string is returned and the standard library data is used. -func parseASN1DNSequenceWithZpkix(data []byte) string { - var rdnSequence zpkix.RDNSequence - var subject zpkix.Name - if _, err := zasn1.Unmarshal(data, &rdnSequence); err != nil { - return "" - } - subject.FillFromRDNSequence(&rdnSequence) - dnParsedString := subject.String() - return dnParsedString -} diff --git a/pkg/tlsx/tlsx.go b/pkg/tlsx/tlsx.go index 3b105358..b6ab0ee4 100644 --- a/pkg/tlsx/tlsx.go +++ b/pkg/tlsx/tlsx.go @@ -8,6 +8,7 @@ import ( "github.com/projectdiscovery/tlsx/pkg/tlsx/auto" "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" "github.com/projectdiscovery/tlsx/pkg/tlsx/jarm" + "github.com/projectdiscovery/tlsx/pkg/tlsx/openssl" "github.com/projectdiscovery/tlsx/pkg/tlsx/tls" "github.com/projectdiscovery/tlsx/pkg/tlsx/ztls" ) @@ -29,6 +30,8 @@ func New(options *clients.Options) (*Service, error) { service.client, err = ztls.New(options) case "ctls": service.client, err = tls.New(options) + case "openssl": + service.client, err = openssl.New(options) case "auto": service.client, err = auto.New(options) default: diff --git a/pkg/tlsx/ztls/ztls.go b/pkg/tlsx/ztls/ztls.go index ad2a224e..b71e01e2 100644 --- a/pkg/tlsx/ztls/ztls.go +++ b/pkg/tlsx/ztls/ztls.go @@ -184,12 +184,12 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C Version: tlsVersion, Cipher: tlsCipher, TLSConnection: "ztls", - CertificateResponse: c.convertCertificateToResponse(hostname, parseSimpleTLSCertificate(hl.ServerCertificates.Certificate)), + CertificateResponse: ConvertCertificateToResponse(c.options, hostname, ParseSimpleTLSCertificate(hl.ServerCertificates.Certificate)), ServerName: config.ServerName, } if c.options.TLSChain { for _, cert := range hl.ServerCertificates.Chain { - response.Chain = append(response.Chain, c.convertCertificateToResponse(hostname, parseSimpleTLSCertificate(cert))) + response.Chain = append(response.Chain, ConvertCertificateToResponse(c.options, hostname, ParseSimpleTLSCertificate(cert))) } } if c.options.Ja3 { @@ -198,12 +198,14 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C return response, nil } -func parseSimpleTLSCertificate(cert tls.SimpleCertificate) *x509.Certificate { +// ParseSimpleTLSCertificate using zcrypto x509 +func ParseSimpleTLSCertificate(cert tls.SimpleCertificate) *x509.Certificate { parsed, _ := x509.ParseCertificate(cert.Raw) return parsed } -func (c *Client) convertCertificateToResponse(hostname string, cert *x509.Certificate) *clients.CertificateResponse { +// ConvertCertificateToResponse using zcrypto x509 +func ConvertCertificateToResponse(options *clients.Options, hostname string, cert *x509.Certificate) *clients.CertificateResponse { if cert == nil { return nil } @@ -228,7 +230,7 @@ func (c *Client) convertCertificateToResponse(hostname string, cert *x509.Certif SHA256: clients.SHA256Fingerprint(cert.Raw), }, } - if c.options.Cert { + if options.Cert { response.Certificate = clients.PemEncode(cert.Raw) } return response