├── .gitignore ├── pkg ├── go-sdk │ ├── README.md │ ├── basic.go │ ├── sdk.go │ ├── random.go │ ├── scan.go │ ├── sdk_test.go │ ├── types.go │ └── protocol.go ├── protocols │ ├── tls │ │ ├── ssl30 │ │ │ ├── serverhellodone.go │ │ │ ├── serverkeyexchange.go │ │ │ ├── sessionid.go │ │ │ ├── cmd │ │ │ │ └── cmd.go │ │ │ ├── marshal.go │ │ │ ├── README.md │ │ │ ├── unmarshal.go │ │ │ ├── clienthello.go │ │ │ ├── certificate.go │ │ │ ├── random.go │ │ │ ├── certificaterequest.go │ │ │ ├── alert.go │ │ │ ├── serverhello.go │ │ │ ├── sslplaintext.go │ │ │ ├── handshake.go │ │ │ └── ssl30.go │ │ ├── tls10 │ │ │ ├── serverhellodone.go │ │ │ ├── serverkeyexchange.go │ │ │ ├── sessionid.go │ │ │ ├── marshal.go │ │ │ ├── README.md │ │ │ ├── cmd │ │ │ │ └── cmd.go │ │ │ ├── unmarshal.go │ │ │ ├── clienthello.go │ │ │ ├── certificate.go │ │ │ ├── random.go │ │ │ ├── certificaterequest.go │ │ │ ├── extensions.go │ │ │ ├── alert.go │ │ │ ├── serverhello.go │ │ │ ├── sslplaintext.go │ │ │ ├── handshake.go │ │ │ └── tls10.go │ │ ├── tls11 │ │ │ ├── serverhellodone.go │ │ │ ├── serverkeyexchange.go │ │ │ ├── sessionid.go │ │ │ ├── marshal.go │ │ │ ├── README.md │ │ │ ├── unmarshal.go │ │ │ ├── cmd │ │ │ │ └── cmd.go │ │ │ ├── clienthello.go │ │ │ ├── certificate.go │ │ │ ├── random.go │ │ │ ├── certificaterequest.go │ │ │ ├── extensions.go │ │ │ ├── alert.go │ │ │ ├── serverhello.go │ │ │ ├── sslplaintext.go │ │ │ ├── handshake.go │ │ │ └── tls11.go │ │ ├── tls12 │ │ │ ├── serverhellodone.go │ │ │ ├── serverkeyexchange.go │ │ │ ├── sessionid.go │ │ │ ├── marshal.go │ │ │ ├── README.md │ │ │ ├── unmarshal.go │ │ │ ├── cmd │ │ │ │ └── cmd.go │ │ │ ├── clienthello.go │ │ │ ├── certificate.go │ │ │ ├── random.go │ │ │ ├── certificaterequest.go │ │ │ ├── alert.go │ │ │ ├── extensions.go │ │ │ ├── serverhello.go │ │ │ ├── sslplaintext.go │ │ │ ├── handshake.go │ │ │ └── tls12.go │ │ ├── ciphersuite │ │ │ ├── README.md │ │ │ ├── ciphersuite.go │ │ │ └── tools │ │ │ │ └── parser.go │ │ ├── tls13 │ │ │ ├── cmd │ │ │ │ └── cmd.go │ │ │ └── tls13.go │ │ ├── tls.go │ │ └── certificate │ │ │ └── certificate.go │ └── dns │ │ ├── README.md │ │ ├── probe.go │ │ ├── TXT.go │ │ ├── A.go │ │ ├── AAAA.go │ │ ├── MX.go │ │ ├── dns_test.go │ │ └── dns.go ├── randomip │ ├── README.md │ ├── LICENSE │ └── randomip.go └── portscan │ ├── host.go │ ├── README.md │ ├── portscan.go │ ├── result.go │ ├── cmd │ └── cmd.go │ ├── connect.go │ └── udp.go ├── .gitmodules ├── init └── elmasy.service ├── internal ├── api │ ├── ip │ │ └── handler.go │ ├── random │ │ ├── port │ │ │ └── handler.go │ │ └── ip │ │ │ └── handler.go │ ├── protocol │ │ ├── tls │ │ │ ├── utils.go │ │ │ ├── handler.go │ │ │ └── certificate │ │ │ │ └── handler.go │ │ ├── dns │ │ │ ├── utils.go │ │ │ └── handler.go │ │ └── probe │ │ │ └── handler.go │ └── scan │ │ ├── port │ │ ├── handler.go │ │ └── utils.go │ │ ├── handler.go │ │ └── scanner.go ├── utils │ └── utils.go ├── config │ └── config.go └── router │ └── router.go ├── configs └── elmasy.conf ├── cmd └── elmasy │ └── main.go ├── go.mod ├── scripts ├── build.sh └── install.sh ├── README.md └── docs └── beta └── features.md /.gitignore: -------------------------------------------------------------------------------- 1 | ignore/ 2 | ignore/* -------------------------------------------------------------------------------- /pkg/go-sdk/README.md: -------------------------------------------------------------------------------- 1 | # go-sdk 2 | 3 | A dead simple SDK for Go language. -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/serverhellodone.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | type serverHelloDone struct{} 4 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/serverhellodone.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | type serverHelloDone struct{} 4 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/serverhellodone.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | type serverHelloDone struct{} 4 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/serverhellodone.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | type serverHelloDone struct{} 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "frontend"] 2 | path = frontend 3 | url = https://github.com/elmasy-com/frontend.git 4 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ciphersuite/README.md: -------------------------------------------------------------------------------- 1 | # ciphersuite 2 | 3 | The info about ciphersuites mostly comes from [ciphersuite.info](https://ciphersuite.info/). -------------------------------------------------------------------------------- /pkg/protocols/dns/README.md: -------------------------------------------------------------------------------- 1 | # dns 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/elmasy-com/dns.svg)](https://pkg.go.dev/github.com/elmasy-com/dns) 4 | -------------------------------------------------------------------------------- /pkg/randomip/README.md: -------------------------------------------------------------------------------- 1 | # randomip 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/elmasy-com/randomip.svg)](https://pkg.go.dev/github.com/elmasy-com/randomip) 4 | 5 | ## Get 6 | 7 | ```bash 8 | go get github.com/elmasy-com/randomip 9 | ``` 10 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/serverkeyexchange.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | // TODO 4 | 5 | type serverKeyExchange struct { 6 | Body []byte 7 | } 8 | 9 | func unmarshalServerKeyExchange(bytes []byte) (serverKeyExchange, error) { 10 | 11 | return serverKeyExchange{Body: bytes}, nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/serverkeyexchange.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | // TODO 4 | 5 | type serverKeyExchange struct { 6 | Body []byte 7 | } 8 | 9 | func unmarshalServerKeyExchange(bytes []byte) (serverKeyExchange, error) { 10 | 11 | return serverKeyExchange{Body: bytes}, nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/serverkeyexchange.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | // TODO 4 | 5 | type serverKeyExchange struct { 6 | Body []byte 7 | } 8 | 9 | func unmarshalServerKeyExchange(bytes []byte) (serverKeyExchange, error) { 10 | 11 | return serverKeyExchange{Body: bytes}, nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/serverkeyexchange.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | // TODO 4 | 5 | type serverKeyExchange struct { 6 | Body []byte 7 | } 8 | 9 | func unmarshalServerKeyExchange(bytes []byte) (serverKeyExchange, error) { 10 | 11 | return serverKeyExchange{Body: bytes}, nil 12 | } 13 | -------------------------------------------------------------------------------- /init/elmasy.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Elmasy server 3 | 4 | [Service] 5 | Type=simple 6 | User=elmasy 7 | Group=elmasy 8 | WorkingDirectory=/opt/elmasy 9 | ExecStart=/opt/elmasy/elmasy 10 | StandardOutput=append:/var/log/elmasy.log 11 | StandardError=append:/var/log/elmasy.log 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/sessionid.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | /* 8 | opaque SessionID<0..32>; 9 | */ 10 | 11 | func marshalSessionID() []byte { 12 | 13 | buf := bytebuilder.NewEmpty() 14 | 15 | buf.WriteUint8(32) 16 | buf.WriteRandom(32) 17 | 18 | return buf.Bytes() 19 | } 20 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/sessionid.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | /* 8 | opaque SessionID<0..32>; 9 | */ 10 | 11 | func marshalSessionID() []byte { 12 | 13 | buf := bytebuilder.NewEmpty() 14 | 15 | buf.WriteUint8(32) 16 | buf.WriteRandom(32) 17 | 18 | return buf.Bytes() 19 | } 20 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/sessionid.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | /* 8 | opaque SessionID<0..32>; 9 | */ 10 | 11 | func marshalSessionID() []byte { 12 | 13 | buf := bytebuilder.NewEmpty() 14 | 15 | buf.WriteUint8(32) 16 | buf.WriteRandom(32) 17 | 18 | return buf.Bytes() 19 | } 20 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/sessionid.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | /* 8 | opaque SessionID<0..32>; 9 | */ 10 | 11 | func marshalSessionID() []byte { 12 | 13 | buf := bytebuilder.NewEmpty() 14 | 15 | buf.WriteUint8(32) 16 | buf.WriteRandom(32) 17 | 18 | return buf.Bytes() 19 | } 20 | -------------------------------------------------------------------------------- /pkg/portscan/host.go: -------------------------------------------------------------------------------- 1 | package portscan 2 | 3 | import "syscall" 4 | 5 | // GetMaxFD returns the maximum number of open file descriptors. 6 | func GetMaxFD() (int, error) { 7 | 8 | rlimit := syscall.Rlimit{} 9 | 10 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil { 11 | return 0, err 12 | } 13 | 14 | return int(rlimit.Cur), nil 15 | } 16 | -------------------------------------------------------------------------------- /internal/api/ip/handler.go: -------------------------------------------------------------------------------- 1 | package ip 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func Get(c *gin.Context) { 11 | if c.GetHeader("Accept") == "application/json" { 12 | c.JSON(http.StatusOK, sdk.ResultStr{Result: c.ClientIP()}) 13 | } else { 14 | c.String(http.StatusOK, c.ClientIP()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | For manual testing. 5 | */ 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "time" 11 | 12 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ssl30" 13 | ) 14 | 15 | func main() { 16 | 17 | ip := os.Args[1] 18 | port := os.Args[2] 19 | 20 | r, err := ssl30.Probe("tcp", ip, port, 2*time.Second) 21 | if err != nil { 22 | fmt.Fprintf(os.Stderr, "Fail: %s\n", err) 23 | } else { 24 | fmt.Printf("%#v\n", r) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/portscan/README.md: -------------------------------------------------------------------------------- 1 | # portscan 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/elmasy-com/portscan.svg)](https://pkg.go.dev/github.com/elmasy-com/portscan) 4 | 5 | ## StealthScan() 6 | 7 | - SYN Scan (known as Stealth Scan). Equal to `nmap -sS`. 8 | - **Require root** 9 | - Read more: [Nmap Doc](https://nmap.org/book/synscan.html) 10 | 11 | ## ConnectScan() 12 | 13 | - Equal to `nmap -sT` 14 | - Read more: [Nmap Doc](https://nmap.org/book/scan-methods-connect-scan.html) 15 | -------------------------------------------------------------------------------- /configs/elmasy.conf: -------------------------------------------------------------------------------- 1 | # URL of the instance 2 | url: https://elmasy.com 3 | 4 | # Service listen on this ip:port 5 | listen: 127.0.0.1:8080 6 | 7 | # Trusted proxies 8 | trusted_proxies: [127.0.0.1] 9 | 10 | # IP address range (CIDR notation) that cant be touched 11 | blacklisted_networks: 12 | - 127.0.0.0/8 13 | - 10.0.0.0/8 14 | - 172.16.0.0/12 15 | - 192.168.0.0/16 16 | - 224.0.0.0/4 17 | 18 | ssl_certificate: /path/to/cert 19 | ssl_certificate_key: /path/to/key 20 | 21 | # Verbose logging (for development) 22 | verbose: true -------------------------------------------------------------------------------- /internal/api/random/port/handler.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "math/rand" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func Get(c *gin.Context) { 14 | 15 | rand.Seed(time.Now().UnixNano()) 16 | 17 | n := rand.Intn(65534) + 1 18 | 19 | switch c.GetHeader("Accept") { 20 | case "*/*": 21 | c.String(http.StatusOK, "%d", n) 22 | case "application/json": 23 | c.JSON(http.StatusOK, sdk.ResultStr{Result: strconv.Itoa(n)}) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/go-sdk/basic.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | func GetIP() (string, error) { 9 | 10 | body, status, err := Get("/ip") 11 | if err != nil { 12 | return "", err 13 | } 14 | 15 | switch status { 16 | case 200: 17 | result := ResultStr{} 18 | 19 | if err := json.Unmarshal(body, &result); err != nil { 20 | return "", fmt.Errorf("failed to unmarshal: %s", err) 21 | } 22 | return result.Result, nil 23 | default: 24 | return "", fmt.Errorf("unknown status: %d", status) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/marshal.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 4 | 5 | // Shorthand to create a Closure Alert 6 | func createClosureAlert() []byte { 7 | 8 | fragment := marshalAlert(1, 0) 9 | 10 | return marshalSSLPlaintext(21, fragment) 11 | } 12 | 13 | // Shorthand to create a ClientHello 14 | func createPacketClientHello(ciphers []ciphersuite.CipherSuite) []byte { 15 | 16 | body := marshalClientHello(ciphers) 17 | 18 | fragment := marshalHandshake(1, body) 19 | 20 | return marshalSSLPlaintext(22, fragment) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/portscan/portscan.go: -------------------------------------------------------------------------------- 1 | package portscan 2 | 3 | import "time" 4 | 5 | type State uint8 6 | 7 | const ( 8 | OPEN State = iota 9 | CLOSED 10 | FILTERED 11 | ) 12 | 13 | func (s State) String() string { 14 | 15 | switch s { 16 | case OPEN: 17 | return "open" 18 | case CLOSED: 19 | return "closed" 20 | case FILTERED: 21 | return "filtered" 22 | default: 23 | return "unknown" 24 | } 25 | } 26 | 27 | // SuggestTimeout suggests a global timeout based on the number of ports 28 | func SuggestTimeout(n int) time.Duration { 29 | return time.Duration(500+(n*5)) * time.Millisecond 30 | } 31 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/marshal.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 4 | 5 | // Shorthand to create a Closure Alert 6 | func createClosureAlert() []byte { 7 | 8 | fragment := marshalAlert(1, 0) 9 | 10 | return marshalSSLPlaintext(21, fragment) 11 | } 12 | 13 | // Shorthand to create a ClientHello 14 | func createPacketClientHello(ciphers []ciphersuite.CipherSuite, ServerName string) []byte { 15 | 16 | body := marshalClientHello(ciphers, ServerName) 17 | 18 | fragment := marshalHandshake(1, body) 19 | 20 | return marshalSSLPlaintext(22, fragment) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/marshal.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 4 | 5 | // Shorthand to create a Closure Alert 6 | func createClosureAlert() []byte { 7 | 8 | fragment := marshalAlert(1, 0) 9 | 10 | return marshalSSLPlaintext(21, fragment) 11 | } 12 | 13 | // Shorthand to create a ClientHello 14 | func createPacketClientHello(ciphers []ciphersuite.CipherSuite, ServerName string) []byte { 15 | 16 | body := marshalClientHello(ciphers, ServerName) 17 | 18 | fragment := marshalHandshake(1, body) 19 | 20 | return marshalSSLPlaintext(22, fragment) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/marshal.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 4 | 5 | // Shorthand to create a Closure Alert 6 | func createClosureAlert() []byte { 7 | 8 | fragment := marshalAlert(1, 0) 9 | 10 | return marshalSSLPlaintext(21, fragment) 11 | } 12 | 13 | // Shorthand to create a ClientHello 14 | func createPacketClientHello(ciphers []ciphersuite.CipherSuite, ServerName string) []byte { 15 | 16 | body := marshalClientHello(ciphers, ServerName) 17 | 18 | fragment := marshalHandshake(1, body) 19 | 20 | return marshalSSLPlaintext(22, fragment) 21 | } 22 | -------------------------------------------------------------------------------- /internal/api/random/ip/handler.go: -------------------------------------------------------------------------------- 1 | package randomip 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 7 | "github.com/elmasy-com/elmasy/pkg/randomip" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func Get(c *gin.Context) { 12 | 13 | switch version := c.Query("version"); version { 14 | case "ipv4", "4": 15 | c.JSON(http.StatusOK, sdk.ResultStr{Result: randomip.GetPublicIPv4().String()}) 16 | case "ipv6", "6": 17 | c.JSON(http.StatusOK, sdk.ResultStr{Result: randomip.GetPublicIPv6().String()}) 18 | default: 19 | c.JSON(http.StatusOK, sdk.ResultStr{Result: randomip.GetPublicIP().String()}) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/README.md: -------------------------------------------------------------------------------- 1 | # tls11 2 | 3 | Partially implemented TLS 1.1. 4 | 5 | Implement a part of the handshake to get information about the server. 6 | 7 | Relevant RFC: [RFC 4346](https://datatracker.ietf.org/doc/rfc4346/) 8 | 9 | ## Errors silenced 10 | 11 | ### `readServerResponse()` 12 | 13 | - Timeout while reading response. 14 | - Some server send RST (TCP Reset) inmediately after Alert(Handshake Failure) at the initial handshake. This causing a `connection reset by peer` error. But if the response is exactly 7 byte, the handshake was OK, but TLS11 is not supported. 15 | 16 | ### `getSupportedCiphers()` 17 | 18 | - Some server respond an RST without Alert(Handshake Failure) when checking only one cipher. This means that the cipher is not supported. -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/README.md: -------------------------------------------------------------------------------- 1 | # tls12 2 | 3 | Partially implemented TLS 1.2. 4 | 5 | Implement a part of the handshake to get information about the server. 6 | 7 | Relevant RFC: [RFC 5246](https://www.rfc-editor.org/rfc/rfc5246) 8 | 9 | ## Errors silenced 10 | 11 | ### `readServerResponse()` 12 | 13 | - Timeout while reading response. 14 | - Some server send RST (TCP Reset) inmediately after Alert(Handshake Failure) at the initial handshake. This causing a `connection reset by peer` error. But if the response is exactly 7 byte, the handshake was OK, but SSLv3 is not supported. 15 | 16 | ### `getSupportedCiphers()` 17 | 18 | - Some server respond an RST without Alert(Handshake Failure) when checking only one cipher. This means that the cipher is not supported. -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/README.md: -------------------------------------------------------------------------------- 1 | # ssl30 2 | 3 | Partially implemented SSL 3.0. 4 | 5 | Implement a part of the handshake to get information about the server. 6 | 7 | Relevant RFC: [RFC 6101](https://datatracker.ietf.org/doc/html/rfc6101) 8 | 9 | ## Errors silenced 10 | 11 | ### `readServerResponse()` 12 | 13 | - Timeout while reading response. 14 | - Some server send RST (TCP Reset) inmediately after Alert(Handshake Failure) at the initial handshake. This causing a `connection reset by peer` error. But if the response is exactly 7 byte, the handshake was OK, but SSLv3 is not supported. 15 | 16 | ### `getSupportedCiphers()` 17 | 18 | - Some server respond an RST without Alert(Handshake Failure) when checking only one cipher. This means that the cipher is not supported. -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/README.md: -------------------------------------------------------------------------------- 1 | # tls10 2 | 3 | Partially implemented TLS 1.0. 4 | 5 | Implement a part of the handshake to get information about the server. 6 | 7 | Relevant RFC: [RFC 2246](https://datatracker.ietf.org/doc/html/rfc2246) 8 | 9 | ## Errors silenced 10 | 11 | ### `readServerResponse()` 12 | 13 | - Timeout while reading response. 14 | - Some server send RST (TCP Reset) inmediately after Alert(Handshake Failure) at the initial handshake. This causing a `connection reset by peer` error. But if the response is exactly 7 byte, the handshake was OK, but TLS10 is not supported. 15 | 16 | ### `getSupportedCiphers()` 17 | 18 | - Some server respond an RST without Alert(Handshake Failure) when checking only one cipher. This means that the cipher is not supported. -------------------------------------------------------------------------------- /pkg/protocols/dns/probe.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | // Probe checks whether DNS protocol is available on network on ip:port. 11 | // This function do a simple query with "elmasy.com."/"A". 12 | // network must be "tcp", "tcp-tls" or "udp". 13 | func Probe(network, ip, port string, timeout time.Duration) (bool, error) { 14 | 15 | if network != "tcp" && network != "tcp-tls" && network != "udp" { 16 | return false, fmt.Errorf("invalid network: %s", network) 17 | } 18 | 19 | m := new(dns.Msg) 20 | m.SetQuestion("elmasy.com.", dns.TypeA) 21 | 22 | c := new(dns.Client) 23 | 24 | c.Net = network 25 | c.Timeout = timeout 26 | 27 | _, _, err := c.Exchange(m, ip+":"+port) 28 | 29 | return err == nil, nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/protocols/dns/TXT.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | // QueryTXT returns the answer as a string slice. 10 | // Returns nil in case of error. 11 | // This function retries the query in case of error, up to MAX_RETRIES. 12 | func QueryTXT(name string) ([]string, error) { 13 | 14 | var ( 15 | a []dns.RR 16 | r = make([]string, 0) 17 | err error 18 | ) 19 | 20 | for i := 0; i < MAX_RETRIES; i++ { 21 | 22 | a, err = query(name, dns.TypeTXT) 23 | if err == nil { 24 | break 25 | } 26 | } 27 | 28 | for i := range a { 29 | 30 | switch v := a[i].(type) { 31 | case *dns.TXT: 32 | r = append(r, v.Txt...) 33 | default: 34 | return r, fmt.Errorf("unknown type: %T", v) 35 | } 36 | } 37 | 38 | return r, err 39 | 40 | } 41 | -------------------------------------------------------------------------------- /pkg/protocols/dns/A.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | // QueryA returns net.IP structs of the answers. 11 | // Returns nil in case of error. 12 | // This function retries the query in case of error, up to MAX_RETRIES. 13 | func QueryA(name string) ([]net.IP, error) { 14 | 15 | var ( 16 | a []dns.RR 17 | r = make([]net.IP, 0) 18 | err error 19 | ) 20 | 21 | for i := 0; i < MAX_RETRIES; i++ { 22 | 23 | a, err = query(name, dns.TypeA) 24 | if err == nil { 25 | break 26 | } 27 | } 28 | 29 | for i := range a { 30 | 31 | switch v := a[i].(type) { 32 | case *dns.A: 33 | r = append(r, v.A) 34 | case *dns.CNAME: 35 | // Ignore CNAME 36 | continue 37 | default: 38 | return r, fmt.Errorf("unknown type: %T", v) 39 | } 40 | } 41 | 42 | return r, err 43 | } 44 | -------------------------------------------------------------------------------- /pkg/protocols/dns/AAAA.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | // QueryAAAA returns net.IP structs of the answers. 11 | // Returns nil in case of error. 12 | // This function retries the query in case of error, up to MAX_RETRIES. 13 | func QueryAAAA(name string) ([]net.IP, error) { 14 | 15 | var ( 16 | a []dns.RR 17 | r = make([]net.IP, 0) 18 | err error 19 | ) 20 | 21 | for i := 0; i < MAX_RETRIES; i++ { 22 | 23 | a, err = query(name, dns.TypeAAAA) 24 | if err == nil { 25 | break 26 | } 27 | } 28 | 29 | for i := range a { 30 | 31 | switch v := a[i].(type) { 32 | case *dns.AAAA: 33 | r = append(r, v.AAAA) 34 | case *dns.CNAME: 35 | // Ignore CNAME 36 | continue 37 | default: 38 | return r, fmt.Errorf("unknown type: %T", v) 39 | } 40 | } 41 | 42 | return r, err 43 | } 44 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls10" 9 | ) 10 | 11 | func main() { 12 | 13 | ip := "142.132.164.231" 14 | port := "443" 15 | 16 | r, err := tls10.Probe("tcp", ip, port, 2*time.Second, "") 17 | if err != nil { 18 | fmt.Fprintf(os.Stderr, "Fail without SNI: %s\n", err) 19 | } else { 20 | fmt.Printf("%#v\n", r) 21 | } 22 | 23 | r, err = tls10.Probe("tcp", ip, port, 2*time.Second, "danielgorbe.com") 24 | if err != nil { 25 | fmt.Fprintf(os.Stderr, "Fail with invalid SNI: %s\n", err) 26 | } else { 27 | fmt.Printf("%#v\n", r) 28 | } 29 | 30 | r, err = tls10.Probe("tcp", ip, port, 2*time.Second, "elmasy.com") 31 | if err != nil { 32 | fmt.Fprintf(os.Stderr, "Fail with valid SNI: %s\n", err) 33 | } else { 34 | fmt.Printf("%#v\n", r) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/go-sdk/sdk.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | var ( 10 | API_PATH = "https://elmasy.com/api" 11 | USER_AGENT = "Elmasy-SDK" 12 | DefaultClient = &http.Client{} 13 | ) 14 | 15 | // Get query API_PATH + endpoint. 16 | // Returns the body, the status code and error. 17 | func Get(endpoint string) ([]byte, int, error) { 18 | 19 | req, err := http.NewRequest("GET", API_PATH+endpoint, nil) 20 | if err != nil { 21 | return nil, 0, fmt.Errorf("failed to create a new request: %s", err) 22 | } 23 | req.Header.Add("Accept", "application/json") 24 | req.Header.Add("User-Agent", USER_AGENT) 25 | 26 | resp, err := DefaultClient.Do(req) 27 | if err != nil { 28 | return nil, 0, fmt.Errorf("failed to query: %s", err) 29 | } 30 | defer resp.Body.Close() 31 | 32 | body, err := io.ReadAll(resp.Body) 33 | 34 | return body, resp.StatusCode, err 35 | } 36 | -------------------------------------------------------------------------------- /internal/api/protocol/tls/utils.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 5 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 6 | "github.com/elmasy-com/identify" 7 | ) 8 | 9 | // Choose a ServerName. 10 | // The priority: servername parameter > target (if valid domain) > "" (empty string) 11 | func getServerName(servername, target string) string { 12 | 13 | if servername != "" { 14 | return servername 15 | } 16 | 17 | if identify.IsDomainName(target) { 18 | return target 19 | } 20 | 21 | return "" 22 | } 23 | 24 | // convertCiphers converts []ciphersuite.CipherSuite to []sdk.Cipher 25 | func convertCiphers(ciphers []ciphersuite.CipherSuite) []sdk.Cipher { 26 | 27 | r := make([]sdk.Cipher, 0) 28 | 29 | for i := range ciphers { 30 | r = append(r, sdk.Cipher{Name: ciphers[i].Name, Security: ciphers[i].Security}) 31 | } 32 | 33 | return r 34 | } 35 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/unmarshal.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | func unmarshalResponse(bytes []byte) (SSL30, error) { 8 | 9 | var ( 10 | buf = bytebuilder.NewBuffer(bytes) 11 | result = SSL30{} 12 | messages []interface{} 13 | ) 14 | 15 | for !buf.Empty() { 16 | 17 | message, err := unmarshalSSLPlaintext(&buf) 18 | if err != nil { 19 | return result, err 20 | } 21 | 22 | messages = append(messages, message...) 23 | 24 | } 25 | 26 | for i := range messages { 27 | 28 | switch message := messages[i].(type) { 29 | case alert: 30 | result.Supported = false 31 | return result, nil 32 | case serverHello: 33 | result.Supported = true 34 | result.DefaultCipher = message.CipherSuite 35 | case certificate: 36 | result.Certificates = message.Certificates 37 | 38 | } 39 | } 40 | 41 | return result, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/unmarshal.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | func unmarshalResponse(bytes []byte) (TLS10, error) { 8 | 9 | var ( 10 | buf = bytebuilder.NewBuffer(bytes) 11 | result = TLS10{} 12 | messages []interface{} 13 | ) 14 | 15 | for !buf.Empty() { 16 | 17 | message, err := unmarshalSSLPlaintext(&buf) 18 | if err != nil { 19 | return result, err 20 | } 21 | 22 | messages = append(messages, message...) 23 | 24 | } 25 | 26 | for i := range messages { 27 | 28 | switch message := messages[i].(type) { 29 | case alert: 30 | result.Supported = false 31 | return result, nil 32 | case serverHello: 33 | result.Supported = true 34 | result.DefaultCipher = message.CipherSuite 35 | case certificate: 36 | result.Certificates = message.Certificates 37 | 38 | } 39 | } 40 | 41 | return result, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/unmarshal.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | func unmarshalResponse(bytes []byte) (TLS11, error) { 8 | 9 | var ( 10 | buf = bytebuilder.NewBuffer(bytes) 11 | result = TLS11{} 12 | messages []interface{} 13 | ) 14 | 15 | for !buf.Empty() { 16 | 17 | message, err := unmarshalSSLPlaintext(&buf) 18 | if err != nil { 19 | return result, err 20 | } 21 | 22 | messages = append(messages, message...) 23 | 24 | } 25 | 26 | for i := range messages { 27 | 28 | switch message := messages[i].(type) { 29 | case alert: 30 | result.Supported = false 31 | return result, nil 32 | case serverHello: 33 | result.Supported = true 34 | result.DefaultCipher = message.CipherSuite 35 | case certificate: 36 | result.Certificates = message.Certificates 37 | 38 | } 39 | } 40 | 41 | return result, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/unmarshal.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | ) 6 | 7 | func unmarshalResponse(bytes []byte) (TLS12, error) { 8 | 9 | var ( 10 | buf = bytebuilder.NewBuffer(bytes) 11 | result = TLS12{} 12 | messages []interface{} 13 | ) 14 | 15 | for !buf.Empty() { 16 | 17 | message, err := unmarshalSSLPlaintext(&buf) 18 | if err != nil { 19 | return result, err 20 | } 21 | 22 | messages = append(messages, message...) 23 | 24 | } 25 | 26 | for i := range messages { 27 | 28 | switch message := messages[i].(type) { 29 | case alert: 30 | result.Supported = false 31 | return result, nil 32 | case serverHello: 33 | result.Supported = true 34 | result.DefaultCipher = message.CipherSuite 35 | case certificate: 36 | result.Certificates = message.Certificates 37 | 38 | } 39 | } 40 | 41 | return result, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/clienthello.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 6 | ) 7 | 8 | /* 9 | struct { 10 | uint32 gmt_unix_time; 11 | opaque random_bytes[28]; 12 | } Random; 13 | 14 | struct { 15 | ProtocolVersion client_version; 16 | Random random; 17 | SessionID session_id; 18 | CipherSuite cipher_suites<2..2^16-1>; 19 | CompressionMethod compression_methods<1..2^8-1>; 20 | } ClientHello; 21 | */ 22 | 23 | func marshalClientHello(ciphers []ciphersuite.CipherSuite) []byte { 24 | 25 | buf := bytebuilder.NewEmpty() 26 | 27 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 28 | 29 | buf.WriteBytes(marshalRandom()...) 30 | 31 | buf.WriteBytes(marshalSessionID()...) 32 | 33 | buf.WriteVector(ciphersuite.Marshal(ciphers), 16) 34 | 35 | buf.WriteVector([]byte{0}, 8) 36 | 37 | return buf.Bytes() 38 | } 39 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Manual testing 5 | */ 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "time" 11 | 12 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls11" 13 | ) 14 | 15 | func main() { 16 | 17 | ip := "142.132.164.231" 18 | port := "443" 19 | 20 | r, err := tls11.Probe("tcp", ip, port, 2*time.Second, "") 21 | if err != nil { 22 | fmt.Fprintf(os.Stderr, "Fail without SNI: %s\n", err) 23 | } else { 24 | fmt.Printf("%#v\n", r) 25 | } 26 | 27 | r, err = tls11.Probe("tcp", ip, port, 2*time.Second, "danielgorbe.com") 28 | if err != nil { 29 | fmt.Fprintf(os.Stderr, "Fail with invalid SNI: %s\n", err) 30 | } else { 31 | fmt.Printf("%#v\n", r) 32 | } 33 | 34 | r, err = tls11.Probe("tcp", ip, port, 2*time.Second, "elmasy.com") 35 | if err != nil { 36 | fmt.Fprintf(os.Stderr, "Fail with valid SNI: %s\n", err) 37 | } else { 38 | fmt.Printf("%#v\n", r) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/protocols/dns/MX.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | type MX struct { 10 | Preference int // Priority 11 | Exchange string // Server's hostname 12 | } 13 | 14 | // QueryMX returns MX structs of the answers. 15 | // Returns nil in case of error. 16 | // This function retries the query in case of error, up to MAX_RETRIES. 17 | func QueryMX(name string) ([]MX, error) { 18 | 19 | var ( 20 | a []dns.RR 21 | r = make([]MX, 0) 22 | err error 23 | ) 24 | 25 | for i := 0; i < MAX_RETRIES; i++ { 26 | 27 | a, err = query(name, dns.TypeMX) 28 | if err == nil { 29 | break 30 | } 31 | } 32 | 33 | for i := range a { 34 | 35 | switch v := a[i].(type) { 36 | case *dns.MX: 37 | r = append(r, MX{Preference: int(v.Preference), Exchange: v.Mx}) 38 | default: 39 | return r, fmt.Errorf("unknown type: %T", v) 40 | } 41 | } 42 | 43 | return r, err 44 | 45 | } 46 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Manual testing: go run . 5 | */ 6 | import ( 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls12" 12 | ) 13 | 14 | func main() { 15 | 16 | ip := "142.132.164.231" 17 | port := "443" 18 | 19 | r, err := tls12.Probe("tcp", ip, port, 2*time.Second, "") 20 | if err != nil { 21 | fmt.Fprintf(os.Stderr, "Fail without SNI: %s\n", err) 22 | } else { 23 | fmt.Printf("%#v\n", r) 24 | } 25 | 26 | r, err = tls12.Probe("tcp", ip, port, 2*time.Second, "danielgorbe.com") 27 | if err != nil { 28 | fmt.Fprintf(os.Stderr, "Fail with invalid SNI: %s\n", err) 29 | } else { 30 | fmt.Printf("%#v\n", r) 31 | } 32 | 33 | r, err = tls12.Probe("tcp", ip, port, 2*time.Second, "elmasy.com") 34 | if err != nil { 35 | fmt.Fprintf(os.Stderr, "Fail with valid SNI: %s\n", err) 36 | } else { 37 | fmt.Printf("%#v\n", r) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls13/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Manual testing: go run . 5 | */ 6 | import ( 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls13" 12 | ) 13 | 14 | func main() { 15 | 16 | ip := "[2a01:4f9:c010:81b5::1]" 17 | port := "443" 18 | 19 | r, err := tls13.Probe("tcp", ip, port, 2*time.Second, "") 20 | if err != nil { 21 | fmt.Fprintf(os.Stderr, "Fail without SNI: %s\n", err) 22 | } else { 23 | fmt.Printf("%#v\n", r) 24 | } 25 | 26 | r, err = tls13.Probe("tcp", ip, port, 2*time.Second, "danielgorbe.com") 27 | if err != nil { 28 | fmt.Fprintf(os.Stderr, "Fail with invalid SNI: %s\n", err) 29 | } else { 30 | fmt.Printf("%#v\n", r) 31 | } 32 | 33 | r, err = tls13.Probe("tcp", ip, port, 2*time.Second, "elmasy.com") 34 | if err != nil { 35 | fmt.Fprintf(os.Stderr, "Fail with valid SNI: %s\n", err) 36 | } else { 37 | fmt.Printf("%#v\n", r) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cmd/elmasy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/elmasy-com/elmasy/internal/config" 9 | "github.com/elmasy-com/elmasy/internal/router" 10 | ) 11 | 12 | func main() { 13 | 14 | confPath := flag.String("config", "./elmasy.conf", "Path to the config file") 15 | flag.Parse() 16 | 17 | if err := config.ParseConfig(*confPath); err != nil { 18 | fmt.Fprintf(os.Stderr, "Failed to parse config: %s\n", err) 19 | os.Exit(1) 20 | } 21 | 22 | r := router.SetupRouter() 23 | 24 | if config.GlobalConfig.SSLCertificate == "" || config.GlobalConfig.SSLKey == "" { 25 | if err := r.Run(config.GlobalConfig.Listen); err != nil { 26 | fmt.Fprintf(os.Stderr, "Failed to run server: %s\n", err) 27 | } 28 | } else { 29 | err := r.RunTLS(config.GlobalConfig.Listen, config.GlobalConfig.SSLCertificate, config.GlobalConfig.SSLKey) 30 | if err != nil { 31 | fmt.Fprintf(os.Stderr, "Failed to run server: %s\n", err) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/clienthello.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 6 | ) 7 | 8 | /* 9 | struct { 10 | uint32 gmt_unix_time; 11 | opaque random_bytes[28]; 12 | } Random; 13 | 14 | struct { 15 | ProtocolVersion client_version; 16 | Random random; 17 | SessionID session_id; 18 | CipherSuite cipher_suites<2..2^16-1>; 19 | CompressionMethod compression_methods<1..2^8-1>; 20 | } ClientHello; 21 | */ 22 | 23 | func marshalClientHello(ciphers []ciphersuite.CipherSuite, ServerName string) []byte { 24 | 25 | buf := bytebuilder.NewEmpty() 26 | 27 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 28 | 29 | buf.WriteBytes(marshalRandom()...) 30 | 31 | buf.WriteBytes(marshalSessionID()...) 32 | 33 | buf.WriteVector(ciphersuite.Marshal(ciphers), 16) 34 | 35 | buf.WriteVector([]byte{0}, 8) 36 | exts := marshalExtensions(ServerName) 37 | 38 | // Extensions length 39 | buf.WriteUint16(uint16(len(exts))) 40 | buf.WriteBytes(exts...) 41 | 42 | return buf.Bytes() 43 | } 44 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/clienthello.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 6 | ) 7 | 8 | /* 9 | struct { 10 | uint32 gmt_unix_time; 11 | opaque random_bytes[28]; 12 | } Random; 13 | 14 | struct { 15 | ProtocolVersion client_version; 16 | Random random; 17 | SessionID session_id; 18 | CipherSuite cipher_suites<2..2^16-1>; 19 | CompressionMethod compression_methods<1..2^8-1>; 20 | } ClientHello; 21 | */ 22 | 23 | func marshalClientHello(ciphers []ciphersuite.CipherSuite, ServerName string) []byte { 24 | 25 | buf := bytebuilder.NewEmpty() 26 | 27 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 28 | 29 | buf.WriteBytes(marshalRandom()...) 30 | 31 | buf.WriteBytes(marshalSessionID()...) 32 | 33 | buf.WriteVector(ciphersuite.Marshal(ciphers), 16) 34 | 35 | buf.WriteVector([]byte{0}, 8) 36 | 37 | exts := marshalExtensions(ServerName) 38 | 39 | // Extensions length 40 | buf.WriteUint16(uint16(len(exts))) 41 | buf.WriteBytes(exts...) 42 | 43 | return buf.Bytes() 44 | } 45 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/clienthello.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "github.com/elmasy-com/bytebuilder" 5 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 6 | ) 7 | 8 | /* 9 | struct { 10 | uint32 gmt_unix_time; 11 | opaque random_bytes[28]; 12 | } Random; 13 | 14 | struct { 15 | ProtocolVersion client_version; 16 | Random random; 17 | SessionID session_id; 18 | CipherSuite cipher_suites<2..2^16-1>; 19 | CompressionMethod compression_methods<1..2^8-1>; 20 | } ClientHello; 21 | */ 22 | 23 | func marshalClientHello(ciphers []ciphersuite.CipherSuite, ServerName string) []byte { 24 | 25 | buf := bytebuilder.NewEmpty() 26 | 27 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 28 | 29 | buf.WriteBytes(marshalRandom()...) 30 | 31 | buf.WriteBytes(marshalSessionID()...) 32 | 33 | buf.WriteVector(ciphersuite.Marshal(ciphers), 16) 34 | 35 | buf.WriteVector([]byte{0}, 8) 36 | 37 | exts := marshalExtensions(ServerName) 38 | 39 | // Extensions length 40 | buf.WriteUint16(uint16(len(exts))) 41 | buf.WriteBytes(exts...) 42 | 43 | return buf.Bytes() 44 | } 45 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/certificate.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | opaque ASN.1Cert<1..2^24-1>; 12 | struct { 13 | ASN.1Cert certificate_list<1..2^24-1>; 14 | } Certificate; 15 | */ 16 | 17 | type certificate struct { 18 | Certificates []x509.Certificate 19 | } 20 | 21 | func unmarhsalCertificate(bytes []byte) (certificate, error) { 22 | 23 | var ( 24 | cert certificate 25 | ok bool 26 | buf = bytebuilder.NewBuffer(bytes) 27 | ) 28 | 29 | if ok = buf.Skip(3); !ok { 30 | return cert, fmt.Errorf("failed to skip certificate_list length") 31 | } 32 | 33 | for !buf.Empty() { 34 | 35 | certbytes, ok := buf.ReadVector(24) 36 | if !ok { 37 | return cert, fmt.Errorf("failed to read certificate vector") 38 | } 39 | 40 | cer, err := x509.ParseCertificate(certbytes) 41 | if err != nil { 42 | return cert, fmt.Errorf("failed to parse certificate: %s", err) 43 | } 44 | 45 | cert.Certificates = append(cert.Certificates, *cer) 46 | } 47 | 48 | return cert, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/certificate.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | opaque ASN.1Cert<1..2^24-1>; 12 | struct { 13 | ASN.1Cert certificate_list<1..2^24-1>; 14 | } Certificate; 15 | */ 16 | 17 | type certificate struct { 18 | Certificates []x509.Certificate 19 | } 20 | 21 | func unmarhsalCertificate(bytes []byte) (certificate, error) { 22 | 23 | var ( 24 | cert certificate 25 | ok bool 26 | buf = bytebuilder.NewBuffer(bytes) 27 | ) 28 | 29 | if ok = buf.Skip(3); !ok { 30 | return cert, fmt.Errorf("failed to skip certificate_list length") 31 | } 32 | 33 | for !buf.Empty() { 34 | 35 | certbytes, ok := buf.ReadVector(24) 36 | if !ok { 37 | return cert, fmt.Errorf("failed to read certificate vector") 38 | } 39 | 40 | cer, err := x509.ParseCertificate(certbytes) 41 | if err != nil { 42 | return cert, fmt.Errorf("failed to parse certificate: %s", err) 43 | } 44 | 45 | cert.Certificates = append(cert.Certificates, *cer) 46 | } 47 | 48 | return cert, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/certificate.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | opaque ASN.1Cert<1..2^24-1>; 12 | struct { 13 | ASN.1Cert certificate_list<1..2^24-1>; 14 | } Certificate; 15 | */ 16 | 17 | type certificate struct { 18 | Certificates []x509.Certificate 19 | } 20 | 21 | func unmarhsalCertificate(bytes []byte) (certificate, error) { 22 | 23 | var ( 24 | cert certificate 25 | ok bool 26 | buf = bytebuilder.NewBuffer(bytes) 27 | ) 28 | 29 | if ok = buf.Skip(3); !ok { 30 | return cert, fmt.Errorf("failed to skip certificate_list length") 31 | } 32 | 33 | for !buf.Empty() { 34 | 35 | certbytes, ok := buf.ReadVector(24) 36 | if !ok { 37 | return cert, fmt.Errorf("failed to read certificate vector") 38 | } 39 | 40 | cer, err := x509.ParseCertificate(certbytes) 41 | if err != nil { 42 | return cert, fmt.Errorf("failed to parse certificate: %s", err) 43 | } 44 | 45 | cert.Certificates = append(cert.Certificates, *cer) 46 | } 47 | 48 | return cert, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/certificate.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | opaque ASN.1Cert<1..2^24-1>; 12 | struct { 13 | ASN.1Cert certificate_list<1..2^24-1>; 14 | } Certificate; 15 | */ 16 | 17 | type certificate struct { 18 | Certificates []x509.Certificate 19 | } 20 | 21 | func unmarhsalCertificate(bytes []byte) (certificate, error) { 22 | 23 | var ( 24 | cert certificate 25 | ok bool 26 | buf = bytebuilder.NewBuffer(bytes) 27 | ) 28 | 29 | if ok = buf.Skip(3); !ok { 30 | return cert, fmt.Errorf("failed to skip certificate_list length") 31 | } 32 | 33 | for !buf.Empty() { 34 | 35 | certbytes, ok := buf.ReadVector(24) 36 | if !ok { 37 | return cert, fmt.Errorf("failed to read certificate vector") 38 | } 39 | 40 | cer, err := x509.ParseCertificate(certbytes) 41 | if err != nil { 42 | return cert, fmt.Errorf("failed to parse certificate: %s", err) 43 | } 44 | 45 | cert.Certificates = append(cert.Certificates, *cer) 46 | } 47 | 48 | return cert, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/random.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | struct { 12 | uint32 gmt_unix_time; 13 | opaque random_bytes[28]; 14 | } Random; 15 | */ 16 | 17 | type random struct { 18 | Time time.Time 19 | RandomBytes []byte 20 | } 21 | 22 | func marshalRandom() []byte { 23 | 24 | buf := bytebuilder.NewEmpty() 25 | 26 | buf.WriteGMTUnixTime32(time.Now()) 27 | 28 | buf.WriteRandom(28) 29 | 30 | return buf.Bytes() 31 | } 32 | 33 | func unmarshalRandom(bytes []byte) (random, error) { 34 | 35 | if bytes == nil { 36 | return random{}, fmt.Errorf("bytes is nil") 37 | } 38 | 39 | var ( 40 | random random 41 | ok bool 42 | buf = bytebuilder.NewBuffer(bytes) 43 | ) 44 | 45 | if random.Time, ok = buf.ReadGMTUnixTime32(); !ok { 46 | return random, fmt.Errorf("failed to read Random") 47 | } 48 | 49 | if random.RandomBytes = buf.ReadBytes(28); random.RandomBytes == nil { 50 | return random, fmt.Errorf("failed to read RandomBytes") 51 | } 52 | 53 | return random, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/random.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | struct { 12 | uint32 gmt_unix_time; 13 | opaque random_bytes[28]; 14 | } Random; 15 | */ 16 | 17 | type random struct { 18 | Time time.Time 19 | RandomBytes []byte 20 | } 21 | 22 | func marshalRandom() []byte { 23 | 24 | buf := bytebuilder.NewEmpty() 25 | 26 | buf.WriteGMTUnixTime32(time.Now()) 27 | 28 | buf.WriteRandom(28) 29 | 30 | return buf.Bytes() 31 | } 32 | 33 | func unmarshalRandom(bytes []byte) (random, error) { 34 | 35 | if bytes == nil { 36 | return random{}, fmt.Errorf("bytes is nil") 37 | } 38 | 39 | var ( 40 | random random 41 | ok bool 42 | buf = bytebuilder.NewBuffer(bytes) 43 | ) 44 | 45 | if random.Time, ok = buf.ReadGMTUnixTime32(); !ok { 46 | return random, fmt.Errorf("failed to read Random") 47 | } 48 | 49 | if random.RandomBytes = buf.ReadBytes(28); random.RandomBytes == nil { 50 | return random, fmt.Errorf("failed to read RandomBytes") 51 | } 52 | 53 | return random, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/random.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | struct { 12 | uint32 gmt_unix_time; 13 | opaque random_bytes[28]; 14 | } Random; 15 | */ 16 | 17 | type random struct { 18 | Time time.Time 19 | RandomBytes []byte 20 | } 21 | 22 | func marshalRandom() []byte { 23 | 24 | buf := bytebuilder.NewEmpty() 25 | 26 | buf.WriteGMTUnixTime32(time.Now()) 27 | 28 | buf.WriteRandom(28) 29 | 30 | return buf.Bytes() 31 | } 32 | 33 | func unmarshalRandom(bytes []byte) (random, error) { 34 | 35 | if bytes == nil { 36 | return random{}, fmt.Errorf("bytes is nil") 37 | } 38 | 39 | var ( 40 | random random 41 | ok bool 42 | buf = bytebuilder.NewBuffer(bytes) 43 | ) 44 | 45 | if random.Time, ok = buf.ReadGMTUnixTime32(); !ok { 46 | return random, fmt.Errorf("failed to read Random") 47 | } 48 | 49 | if random.RandomBytes = buf.ReadBytes(28); random.RandomBytes == nil { 50 | return random, fmt.Errorf("failed to read RandomBytes") 51 | } 52 | 53 | return random, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/random.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | ) 9 | 10 | /* 11 | struct { 12 | uint32 gmt_unix_time; 13 | opaque random_bytes[28]; 14 | } Random; 15 | */ 16 | 17 | type random struct { 18 | Time time.Time 19 | RandomBytes []byte 20 | } 21 | 22 | func marshalRandom() []byte { 23 | 24 | buf := bytebuilder.NewEmpty() 25 | 26 | buf.WriteGMTUnixTime32(time.Now()) 27 | 28 | buf.WriteRandom(28) 29 | 30 | return buf.Bytes() 31 | } 32 | 33 | func unmarshalRandom(bytes []byte) (random, error) { 34 | 35 | if bytes == nil { 36 | return random{}, fmt.Errorf("bytes is nil") 37 | } 38 | 39 | var ( 40 | random random 41 | ok bool 42 | buf = bytebuilder.NewBuffer(bytes) 43 | ) 44 | 45 | if random.Time, ok = buf.ReadGMTUnixTime32(); !ok { 46 | return random, fmt.Errorf("failed to read Random") 47 | } 48 | 49 | if random.RandomBytes = buf.ReadBytes(28); random.RandomBytes == nil { 50 | return random, fmt.Errorf("failed to read RandomBytes") 51 | } 52 | 53 | return random, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/randomip/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniel Gorbe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/protocols/dns/dns_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestQueryA(t *testing.T) { 9 | 10 | MAX_RETRIES = 10 11 | 12 | _, err := QueryA("elmasy.com") 13 | if err != nil { 14 | t.Fatalf("TestQueryA failed: %s\n", err) 15 | } 16 | 17 | } 18 | 19 | func TestQueryAAAA(t *testing.T) { 20 | 21 | MAX_RETRIES = 10 22 | 23 | _, err := QueryAAAA("elmasy.com") 24 | if err != nil { 25 | t.Fatalf("TestQueryAAAA failed: %s\n", err) 26 | } 27 | } 28 | 29 | func TestQueryMX(t *testing.T) { 30 | 31 | MAX_RETRIES = 10 32 | 33 | _, err := QueryMX("elmasy.com") 34 | if err != nil { 35 | t.Fatalf("TestQueryMX failed: %s\n", err) 36 | } 37 | } 38 | 39 | func TestQueryTXT(t *testing.T) { 40 | 41 | MAX_RETRIES = 10 42 | 43 | _, err := QueryTXT("elmasy.com") 44 | if err != nil { 45 | t.Fatalf("TestQueryTXT failed: %s\n", err) 46 | } 47 | } 48 | 49 | func TestProbe(t *testing.T) { 50 | 51 | e, err := Probe("udp", "1.1.1.1", "53", 2*time.Second) 52 | if err != nil { 53 | t.Fatalf("FAIL: %s", err) 54 | } 55 | 56 | if !e { 57 | t.Fatalf("TestProbe failed: 1.1.1.1:53 should be a valid DNS server") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/portscan/result.go: -------------------------------------------------------------------------------- 1 | package portscan 2 | 3 | type Port struct { 4 | Port int 5 | State State 6 | } 7 | type Result []Port 8 | 9 | // GetPortsInt returns []int{ports} from r with state s. 10 | func (r *Result) GetPortsInt(s State) []int { 11 | 12 | v := make([]int, 0) 13 | 14 | for i := range *r { 15 | if (*r)[i].State == s { 16 | v = append(v, (*r)[i].Port) 17 | } 18 | } 19 | 20 | return v 21 | } 22 | 23 | // GetPorts returns []Port from r with state s. 24 | func (r *Result) GetPorts(s State) []Port { 25 | 26 | v := make([]Port, 0) 27 | 28 | for i := range *r { 29 | if (*r)[i].State == s { 30 | v = append(v, (*r)[i]) 31 | } 32 | } 33 | 34 | return v 35 | } 36 | 37 | // Len return the number of ports with state s. 38 | func (r *Result) Len(s State) int { 39 | 40 | v := 0 41 | 42 | for i := range *r { 43 | if (*r)[i].State == s { 44 | v++ 45 | } 46 | } 47 | 48 | return v 49 | } 50 | 51 | // Add result to port. 52 | // If port is not in the scanned ports, silently ignore it 53 | func (r *Result) addResult(port int, state State) { 54 | 55 | for i := range *r { 56 | if (*r)[i].Port == port { 57 | (*r)[i].State = state 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/go-sdk/random.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | func GetRandomIP(version string) (string, error) { 9 | 10 | body, status, err := Get("/random/ip?version=%s" + version) 11 | if err != nil { 12 | return "", err 13 | } 14 | 15 | switch status { 16 | case 200: 17 | r := ResultStr{} 18 | 19 | if err := json.Unmarshal(body, &r); err != nil { 20 | return "", fmt.Errorf("failed to unmarshal: %s", err) 21 | } 22 | return r.Result, nil 23 | case 400: 24 | e := Error{} 25 | 26 | if err := json.Unmarshal(body, &e); err != nil { 27 | return "", fmt.Errorf("failed to unmarshal: %s", err) 28 | } 29 | return "", e 30 | default: 31 | return "", fmt.Errorf("unknown status: %d", status) 32 | } 33 | } 34 | 35 | func GetRandomPort() (string, error) { 36 | 37 | body, status, err := Get("/random/port") 38 | if err != nil { 39 | return "", err 40 | } 41 | 42 | switch status { 43 | case 200: 44 | r := ResultStr{} 45 | 46 | if err := json.Unmarshal(body, &r); err != nil { 47 | return "", fmt.Errorf("failed to unmarshal: %s", err) 48 | } 49 | return r.Result, nil 50 | default: 51 | return "", fmt.Errorf("unknown status: %d", status) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /internal/api/protocol/dns/utils.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net/http" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/elmasy-com/elmasy/pkg/protocols/dns" 9 | ) 10 | 11 | // mxToString sort the MX records by preference (or by name if preferences are equal) 12 | // and returns the names (Exchange) as a string slice 13 | func mxToString(mxs []dns.MX) []string { 14 | 15 | r := make([]string, 0) 16 | 17 | // If preference is equal, compare the name 18 | sort.Slice(mxs, func(i, j int) bool { 19 | if mxs[i].Preference == mxs[j].Preference { 20 | return mxs[i].Exchange < mxs[j].Exchange 21 | } 22 | return mxs[i].Preference < mxs[j].Preference 23 | }) 24 | 25 | for i := range mxs { 26 | // MX returns fqdn so trim the trailing "." 27 | r = append(r, strings.TrimSuffix(mxs[i].Exchange, ".")) 28 | } 29 | 30 | return r 31 | } 32 | 33 | // Returns the status code accoding to the error message 34 | func getStatusCode(err error) int { 35 | 36 | switch err.Error() { 37 | case "FORMERR", "SERVFAIL", "NOTIMP", "REFUSED", "YXDOMAIN", "XRRSET", "NOTAUTH", "NOTZONE": 38 | return http.StatusInternalServerError 39 | case "NXDOMAIN": 40 | return http.StatusNotFound 41 | default: 42 | return http.StatusInternalServerError 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 8 | "github.com/elmasy-com/identify" 9 | ) 10 | 11 | // Lookup46 do a IPv4/IPv6 lookup on the domain. 12 | // Return NXDOMAIN if both 4 and 6 is NXDOMAIN. 13 | // This function adds brackets around IPv6 addresses. 14 | func Lookup46(domain string) ([]string, error) { 15 | 16 | result := make([]string, 0) 17 | 18 | t, err := sdk.DNSLookup("A", domain) 19 | if err != nil && err.Error() != "NXDOMAIN" { 20 | return nil, err 21 | } else { 22 | result = append(result, t...) 23 | } 24 | 25 | t, err = sdk.DNSLookup("AAAA", domain) 26 | if err != nil && err.Error() != "NXDOMAIN" { 27 | return nil, err 28 | } else { 29 | result = append(result, t...) 30 | } 31 | 32 | if len(result) == 0 { 33 | return nil, fmt.Errorf("NXDOMAIN") 34 | } 35 | 36 | return result, nil 37 | } 38 | 39 | // IPv6BracketAdd add brackets around an IPv6 address. 40 | // If ip is an IPv4 address, returns it as is. 41 | func IPv6BracketAdd(ip string) string { 42 | 43 | // Add brackets to IPv6 addresses 44 | if identify.IsValidIPv6(ip) { 45 | return "[" + ip + "]" 46 | } 47 | 48 | return ip 49 | } 50 | 51 | // IPv6BracketRemove removes brackets around an IPv6 address. 52 | // If ip is an IPv4 address, returns it as is. 53 | func IPv6BracketRemove(ip string) string { 54 | 55 | return strings.Trim(ip, "[]") 56 | } 57 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/certificaterequest.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4), 12 | rsa_ephemeral_dh(5), dss_ephemeral_dh(6), fortezza_kea(20), 13 | (255) 14 | } ClientCertificateType; 15 | 16 | opaque DistinguishedName<1..2^16-1>; 17 | 18 | struct { 19 | ClientCertificateType certificate_types<1..2^8-1>; 20 | DistinguishedName certificate_authorities<3..2^16-1>; 21 | } CertificateRequest; 22 | */ 23 | 24 | type certificateRequest struct { 25 | CertTypes []uint8 26 | CertAuths []byte 27 | } 28 | 29 | func unmarshalCertificateRequest(bytes []byte) (certificateRequest, error) { 30 | 31 | var ( 32 | csr = certificateRequest{} 33 | buf = bytebuilder.NewBuffer(bytes) 34 | tLen uint8 35 | ok bool 36 | ) 37 | 38 | if tLen, ok = buf.ReadUint8(); !ok { 39 | return csr, fmt.Errorf("failed to read certifcate_types length") 40 | } 41 | 42 | for i := 0; i < int(tLen); i++ { 43 | var t uint8 44 | 45 | if t, ok = buf.ReadUint8(); !ok { 46 | return csr, fmt.Errorf("failed to read certificate type") 47 | } 48 | 49 | csr.CertTypes = append(csr.CertTypes, t) 50 | } 51 | 52 | if csr.CertAuths, ok = buf.ReadVector(16); !ok { 53 | return csr, fmt.Errorf("failed to read certificate_authorities") 54 | } 55 | 56 | return csr, nil 57 | 58 | } 59 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/certificaterequest.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4), 12 | rsa_ephemeral_dh(5), dss_ephemeral_dh(6), fortezza_kea(20), 13 | (255) 14 | } ClientCertificateType; 15 | 16 | opaque DistinguishedName<1..2^16-1>; 17 | 18 | struct { 19 | ClientCertificateType certificate_types<1..2^8-1>; 20 | DistinguishedName certificate_authorities<3..2^16-1>; 21 | } CertificateRequest; 22 | */ 23 | 24 | type certificateRequest struct { 25 | CertTypes []uint8 26 | CertAuths []byte 27 | } 28 | 29 | func unmarshalCertificateRequest(bytes []byte) (certificateRequest, error) { 30 | 31 | var ( 32 | csr = certificateRequest{} 33 | buf = bytebuilder.NewBuffer(bytes) 34 | tLen uint8 35 | ok bool 36 | ) 37 | 38 | if tLen, ok = buf.ReadUint8(); !ok { 39 | return csr, fmt.Errorf("failed to read certifcate_types length") 40 | } 41 | 42 | for i := 0; i < int(tLen); i++ { 43 | var t uint8 44 | 45 | if t, ok = buf.ReadUint8(); !ok { 46 | return csr, fmt.Errorf("failed to read certificate type") 47 | } 48 | 49 | csr.CertTypes = append(csr.CertTypes, t) 50 | } 51 | 52 | if csr.CertAuths, ok = buf.ReadVector(16); !ok { 53 | return csr, fmt.Errorf("failed to read certificate_authorities") 54 | } 55 | 56 | return csr, nil 57 | 58 | } 59 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/certificaterequest.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4), 12 | rsa_ephemeral_dh(5), dss_ephemeral_dh(6), fortezza_kea(20), 13 | (255) 14 | } ClientCertificateType; 15 | 16 | opaque DistinguishedName<1..2^16-1>; 17 | 18 | struct { 19 | ClientCertificateType certificate_types<1..2^8-1>; 20 | DistinguishedName certificate_authorities<3..2^16-1>; 21 | } CertificateRequest; 22 | */ 23 | 24 | type certificateRequest struct { 25 | CertTypes []uint8 26 | CertAuths []byte 27 | } 28 | 29 | func unmarshalCertificateRequest(bytes []byte) (certificateRequest, error) { 30 | 31 | var ( 32 | csr = certificateRequest{} 33 | buf = bytebuilder.NewBuffer(bytes) 34 | tLen uint8 35 | ok bool 36 | ) 37 | 38 | if tLen, ok = buf.ReadUint8(); !ok { 39 | return csr, fmt.Errorf("failed to read certifcate_types length") 40 | } 41 | 42 | for i := 0; i < int(tLen); i++ { 43 | var t uint8 44 | 45 | if t, ok = buf.ReadUint8(); !ok { 46 | return csr, fmt.Errorf("failed to read certificate type") 47 | } 48 | 49 | csr.CertTypes = append(csr.CertTypes, t) 50 | } 51 | 52 | if csr.CertAuths, ok = buf.ReadVector(16); !ok { 53 | return csr, fmt.Errorf("failed to read certificate_authorities") 54 | } 55 | 56 | return csr, nil 57 | 58 | } 59 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/certificaterequest.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4), 12 | rsa_ephemeral_dh(5), dss_ephemeral_dh(6), fortezza_kea(20), 13 | (255) 14 | } ClientCertificateType; 15 | 16 | opaque DistinguishedName<1..2^16-1>; 17 | 18 | struct { 19 | ClientCertificateType certificate_types<1..2^8-1>; 20 | DistinguishedName certificate_authorities<3..2^16-1>; 21 | } CertificateRequest; 22 | */ 23 | 24 | type certificateRequest struct { 25 | CertTypes []uint8 26 | CertAuths []byte 27 | } 28 | 29 | func unmarshalCertificateRequest(bytes []byte) (certificateRequest, error) { 30 | 31 | var ( 32 | csr = certificateRequest{} 33 | buf = bytebuilder.NewBuffer(bytes) 34 | tLen uint8 35 | ok bool 36 | ) 37 | 38 | if tLen, ok = buf.ReadUint8(); !ok { 39 | return csr, fmt.Errorf("failed to read certifcate_types length") 40 | } 41 | 42 | for i := 0; i < int(tLen); i++ { 43 | var t uint8 44 | 45 | if t, ok = buf.ReadUint8(); !ok { 46 | return csr, fmt.Errorf("failed to read certificate type") 47 | } 48 | 49 | csr.CertTypes = append(csr.CertTypes, t) 50 | } 51 | 52 | if csr.CertAuths, ok = buf.ReadVector(16); !ok { 53 | return csr, fmt.Errorf("failed to read certificate_authorities") 54 | } 55 | 56 | return csr, nil 57 | 58 | } 59 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/alert.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { warning(1), fatal(2), (255) } AlertLevel; 11 | 12 | enum { 13 | close_notify(0), 14 | unexpected_message(10), 15 | bad_record_mac(20), 16 | decompression_failure(30), 17 | handshake_failure(40), 18 | no_certificate(41), 19 | bad_certificate(42), 20 | unsupported_certificate(43), 21 | certificate_revoked(44), 22 | certificate_expired(45), 23 | certificate_unknown(46), 24 | illegal_parameter (47) 25 | (255) 26 | } AlertDescription; 27 | 28 | struct { 29 | AlertLevel level; 30 | AlertDescription description; 31 | } Alert; 32 | */ 33 | 34 | type alert struct { 35 | Level uint8 36 | Description uint8 37 | } 38 | 39 | func marshalAlert(level, description uint8) []byte { 40 | 41 | return []byte{level, description} 42 | } 43 | 44 | func unmarshalAlert(bytes []byte) (alert, error) { 45 | 46 | var ( 47 | buf = bytebuilder.NewBuffer(bytes) 48 | a alert 49 | ok bool 50 | ) 51 | 52 | if a.Level, ok = buf.ReadUint8(); !ok { 53 | return a, fmt.Errorf("failed to read Level") 54 | } 55 | 56 | if a.Description, ok = buf.ReadUint8(); !ok { 57 | return a, fmt.Errorf("failed to read Description") 58 | } 59 | 60 | if buf.Size() != 0 { 61 | return a, fmt.Errorf("buf is not empty") 62 | } 63 | 64 | return a, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/alert.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { warning(1), fatal(2), (255) } AlertLevel; 11 | 12 | enum { 13 | close_notify(0), 14 | unexpected_message(10), 15 | bad_record_mac(20), 16 | decompression_failure(30), 17 | handshake_failure(40), 18 | no_certificate(41), 19 | bad_certificate(42), 20 | unsupported_certificate(43), 21 | certificate_revoked(44), 22 | certificate_expired(45), 23 | certificate_unknown(46), 24 | illegal_parameter (47) 25 | (255) 26 | } AlertDescription; 27 | 28 | struct { 29 | AlertLevel level; 30 | AlertDescription description; 31 | } Alert; 32 | */ 33 | 34 | type alert struct { 35 | Level uint8 36 | Description uint8 37 | } 38 | 39 | func marshalAlert(level, description uint8) []byte { 40 | 41 | return []byte{level, description} 42 | } 43 | 44 | func unmarshalAlert(bytes []byte) (alert, error) { 45 | 46 | var ( 47 | buf = bytebuilder.NewBuffer(bytes) 48 | a alert 49 | ok bool 50 | ) 51 | 52 | if a.Level, ok = buf.ReadUint8(); !ok { 53 | return a, fmt.Errorf("failed to read Level") 54 | } 55 | 56 | if a.Description, ok = buf.ReadUint8(); !ok { 57 | return a, fmt.Errorf("failed to read Description") 58 | } 59 | 60 | if buf.Size() != 0 { 61 | return a, fmt.Errorf("buf is not empty") 62 | } 63 | 64 | return a, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/extensions.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import "github.com/elmasy-com/bytebuilder" 4 | 5 | func marshalExtensions(ServerName string) []byte { 6 | 7 | // Add extensions by hand (very ugly, i know) to create a solid default. 8 | 9 | buf := bytebuilder.NewEmpty() 10 | 11 | // supported_groups 12 | buf.WriteBytes(0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x1D, 0x00, 0x17, 0x00, 0x1e, 0x00, 0x19, 0x00, 0x18) 13 | 14 | // ec_point_formats 15 | buf.WriteBytes(0x00, 0x0B, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02) 16 | 17 | // signature_algorithms 18 | buf.WriteBytes(0x00, 0x0D, 0x00, 0x2A, 0x00, 0x28, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0A, 0x08, 0x0B, 19 | 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x03, 0x03, 0x03, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02) 20 | 21 | // extended_master_secret 22 | buf.WriteBytes(0x00, 0x17, 0x00, 0x00) 23 | 24 | if ServerName != "" { 25 | buf.WriteBytes(marshalExtensionSNI(ServerName)...) 26 | } 27 | 28 | return buf.Bytes() 29 | } 30 | 31 | func marshalExtensionSNI(ServerName string) []byte { 32 | 33 | buf := bytebuilder.NewEmpty() 34 | 35 | buf.WriteBytes(0x00, 0x00) // type 36 | buf.WriteUint16(uint16(5 + len(ServerName))) 37 | buf.WriteUint16(uint16(3 + len(ServerName))) 38 | buf.WriteBytes(0x00) 39 | buf.WriteUint16(uint16(len(ServerName))) 40 | buf.WriteBytes([]byte(ServerName)...) 41 | 42 | return buf.Bytes() 43 | } 44 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/extensions.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import "github.com/elmasy-com/bytebuilder" 4 | 5 | func marshalExtensions(ServerName string) []byte { 6 | 7 | // Add extensions by hand (very ugly, i know) to create a solid default. 8 | 9 | buf := bytebuilder.NewEmpty() 10 | 11 | // supported_groups 12 | buf.WriteBytes(0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x1D, 0x00, 0x17, 0x00, 0x1e, 0x00, 0x19, 0x00, 0x18) 13 | 14 | // ec_point_formats 15 | buf.WriteBytes(0x00, 0x0B, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02) 16 | 17 | // signature_algorithms 18 | buf.WriteBytes(0x00, 0x0D, 0x00, 0x2A, 0x00, 0x28, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0A, 0x08, 0x0B, 19 | 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x03, 0x03, 0x03, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02) 20 | 21 | // extended_master_secret 22 | buf.WriteBytes(0x00, 0x17, 0x00, 0x00) 23 | 24 | if ServerName != "" { 25 | buf.WriteBytes(marshalExtensionSNI(ServerName)...) 26 | } 27 | 28 | return buf.Bytes() 29 | } 30 | 31 | func marshalExtensionSNI(ServerName string) []byte { 32 | 33 | buf := bytebuilder.NewEmpty() 34 | 35 | buf.WriteBytes(0x00, 0x00) // type 36 | buf.WriteUint16(uint16(5 + len(ServerName))) 37 | buf.WriteUint16(uint16(3 + len(ServerName))) 38 | buf.WriteBytes(0x00) 39 | buf.WriteUint16(uint16(len(ServerName))) 40 | buf.WriteBytes([]byte(ServerName)...) 41 | 42 | return buf.Bytes() 43 | } 44 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/extensions.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import "github.com/elmasy-com/bytebuilder" 4 | 5 | func marshalExtensions(ServerName string) []byte { 6 | 7 | // Add extensions by hand (very ugly, i know) to create a solid default. 8 | 9 | buf := bytebuilder.NewEmpty() 10 | 11 | // supported_groups 12 | buf.WriteBytes(0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x1D, 0x00, 0x17, 0x00, 0x1e, 0x00, 0x19, 0x00, 0x18) 13 | 14 | // ec_point_formats 15 | buf.WriteBytes(0x00, 0x0B, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02) 16 | 17 | // signature_algorithms 18 | buf.WriteBytes(0x00, 0x0D, 0x00, 0x2A, 0x00, 0x28, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0A, 0x08, 0x0B, 19 | 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x03, 0x03, 0x03, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02) 20 | 21 | // extended_master_secret 22 | buf.WriteBytes(0x00, 0x17, 0x00, 0x00) 23 | 24 | if ServerName != "" { 25 | buf.WriteBytes(marshalExtensionSNI(ServerName)...) 26 | } 27 | 28 | return buf.Bytes() 29 | } 30 | 31 | func marshalExtensionSNI(ServerName string) []byte { 32 | 33 | buf := bytebuilder.NewEmpty() 34 | 35 | buf.WriteBytes(0x00, 0x00) // type 36 | buf.WriteUint16(uint16(5 + len(ServerName))) 37 | buf.WriteUint16(uint16(3 + len(ServerName))) 38 | buf.WriteBytes(0x00) 39 | buf.WriteUint16(uint16(len(ServerName))) 40 | buf.WriteBytes([]byte(ServerName)...) 41 | 42 | return buf.Bytes() 43 | } 44 | -------------------------------------------------------------------------------- /pkg/protocols/dns/dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/elmasy-com/identify" 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | var conf *dns.ClientConfig 13 | 14 | // The number of tries in case of error. 15 | var MAX_RETRIES int = 2 16 | 17 | func init() { 18 | 19 | var err error 20 | 21 | conf, err = dns.ClientConfigFromFile("/etc/resolv.conf") 22 | if err != nil { 23 | panic("Failed to parse /etc/resolv.conf: " + err.Error()) 24 | } 25 | 26 | rand.Seed(time.Now().UnixMicro()) 27 | } 28 | 29 | // getServer used to randomize DNS servers. 30 | func getServer() string { 31 | 32 | var r string 33 | 34 | switch len(conf.Servers) { 35 | case 0: 36 | r = "1.1.1.1" 37 | case 1: 38 | r = conf.Servers[0] 39 | default: 40 | r = conf.Servers[rand.Intn(len(conf.Servers))] 41 | } 42 | 43 | if identify.IsValidIPv6(r) { 44 | r = "[" + r + "]" 45 | } 46 | 47 | return r + ":53" 48 | } 49 | 50 | // Generic query for internal use. 51 | // Returns the Answer section. 52 | // In case of error, returns nil. 53 | func query(name string, t uint16) ([]dns.RR, error) { 54 | 55 | name = dns.Fqdn(name) 56 | 57 | msg := new(dns.Msg) 58 | msg.SetQuestion(name, t) 59 | 60 | in, err := dns.Exchange(msg, getServer()) 61 | 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | if in.Rcode != dns.RcodeSuccess { 67 | return nil, fmt.Errorf(dns.RcodeToString[in.Rcode]) 68 | } 69 | 70 | return in.Answer, nil 71 | } 72 | -------------------------------------------------------------------------------- /internal/api/scan/port/handler.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 8 | "github.com/elmasy-com/elmasy/pkg/portscan" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func Get(c *gin.Context) { 13 | 14 | var ( 15 | result portscan.Result 16 | errs []error 17 | ) 18 | params, err := parseQuery(c) 19 | if err != nil { 20 | return 21 | } 22 | 23 | switch params.Technique { 24 | case "stealth", "syn": 25 | result, errs = portscan.StealthScan(params.IP, []int{params.Port}, params.Timeout) 26 | case "connect": 27 | result, errs = portscan.ConnectScan(params.IP, []int{params.Port}, params.Timeout) 28 | case "udp": 29 | result, errs = portscan.UDPScan(params.IP, []int{params.Port}, params.Timeout) 30 | } 31 | 32 | if len(errs) > 0 { 33 | 34 | for i := range errs { 35 | c.Error(errs[i]) 36 | } 37 | 38 | // Return only the first error 39 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: errs[0].Error()}) 40 | return 41 | } 42 | 43 | switch len(result) { 44 | case 0: 45 | err := fmt.Errorf("zero result") 46 | c.Error(err) 47 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: err.Error()}) 48 | case 1: 49 | c.JSON(http.StatusOK, sdk.ResultStr{Result: result[0].State.String()}) 50 | default: 51 | err := fmt.Errorf("Invalid number of result at single port: %d", len(result)) 52 | c.Error(err) 53 | c.Error(fmt.Errorf("%#v", result)) 54 | } 55 | if len(result) != 1 { 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elmasy-com/elmasy 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/elmasy-com/bytebuilder v0.4.3 7 | github.com/elmasy-com/identify v1.0.0 8 | github.com/elmasy-com/slices v0.0.0-20220420084118-01ac765fdcea 9 | github.com/gin-contrib/static v0.0.1 10 | github.com/gin-gonic/gin v1.7.7 11 | github.com/google/gopacket v1.1.19 12 | github.com/miekg/dns v1.1.48 13 | github.com/refraction-networking/utls v1.1.0 14 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f 15 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 16 | gopkg.in/yaml.v2 v2.4.0 17 | ) 18 | 19 | require ( 20 | github.com/gin-contrib/sse v0.1.0 // indirect 21 | github.com/go-playground/locales v0.14.0 // indirect 22 | github.com/go-playground/universal-translator v0.18.0 // indirect 23 | github.com/go-playground/validator/v10 v10.10.1 // indirect 24 | github.com/golang/protobuf v1.5.2 // indirect 25 | github.com/json-iterator/go v1.1.12 // indirect 26 | github.com/leodido/go-urn v1.2.1 // indirect 27 | github.com/mattn/go-isatty v0.0.14 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/ugorji/go/codec v1.2.7 // indirect 31 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect 32 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect 33 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect 34 | golang.org/x/text v0.3.7 // indirect 35 | golang.org/x/tools v0.1.10 // indirect 36 | golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect 37 | google.golang.org/protobuf v1.28.0 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /internal/api/protocol/dns/handler.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 8 | "github.com/elmasy-com/elmasy/pkg/protocols/dns" 9 | "github.com/elmasy-com/identify" 10 | "github.com/elmasy-com/slices" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func Get(c *gin.Context) { 15 | 16 | name := c.Param("name") 17 | if !identify.IsDomainName(name) { 18 | err := fmt.Errorf("Invalid name: %s", name) 19 | c.Error(err) 20 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 21 | return 22 | } 23 | 24 | switch qtype := c.Param("type"); qtype { 25 | case "A": 26 | r, err := dns.QueryA(name) 27 | if err != nil { 28 | c.Error(err) 29 | c.JSON(getStatusCode(err), sdk.Error{Err: err.Error()}) 30 | return 31 | } 32 | 33 | c.JSON(http.StatusOK, sdk.ResultStrs{Results: slices.Strings(r)}) 34 | 35 | case "AAAA": 36 | r, err := dns.QueryAAAA(name) 37 | if err != nil { 38 | c.Error(err) 39 | c.JSON(getStatusCode(err), sdk.Error{Err: err.Error()}) 40 | return 41 | } 42 | 43 | c.JSON(http.StatusOK, sdk.ResultStrs{Results: slices.Strings(r)}) 44 | 45 | case "MX": 46 | r, err := dns.QueryMX(name) 47 | if err != nil { 48 | c.Error(err) 49 | c.JSON(getStatusCode(err), sdk.Error{Err: err.Error()}) 50 | return 51 | } 52 | 53 | c.JSON(http.StatusOK, sdk.ResultStrs{Results: mxToString(r)}) 54 | 55 | case "TXT": 56 | r, err := dns.QueryTXT(name) 57 | if err != nil { 58 | c.Error(err) 59 | c.JSON(getStatusCode(err), sdk.Error{Err: err.Error()}) 60 | return 61 | } 62 | 63 | c.JSON(http.StatusOK, sdk.ResultStrs{Results: r}) 64 | 65 | default: 66 | err := fmt.Errorf("Invalid type: %s", qtype) 67 | c.Error(err) 68 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "strings" 8 | 9 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 10 | "gopkg.in/yaml.v2" 11 | ) 12 | 13 | type TLS struct { 14 | Cert string 15 | Key string 16 | } 17 | 18 | type Config struct { 19 | Verbose bool `yaml:"verbose"` 20 | URL string `yaml:"url"` 21 | Listen string `yaml:"listen"` 22 | TrustedProxies []string `yaml:"trusted_proxies"` 23 | BlackListedNetsStr []string `yaml:"blacklisted_networks"` 24 | SSLCertificate string `yaml:"ssl_certificate"` 25 | SSLKey string `yaml:"ssl_certificate_key"` 26 | BlacklistedNetworks []net.IPNet 27 | } 28 | 29 | var GlobalConfig Config 30 | 31 | func ParseConfig(path string) error { 32 | 33 | bytes, err := ioutil.ReadFile(path) 34 | if err != nil { 35 | return fmt.Errorf("failed to read %s: %s", path, err) 36 | } 37 | 38 | if err := yaml.Unmarshal(bytes, &GlobalConfig); err != nil { 39 | return err 40 | } 41 | 42 | if err := GlobalConfig.convertBlackListedIPs(); err != nil { 43 | return err 44 | } 45 | 46 | // Default URL 47 | if GlobalConfig.URL == "" { 48 | GlobalConfig.URL = "https://elmasy.com" 49 | } else { 50 | GlobalConfig.URL = strings.TrimRight(GlobalConfig.URL, "/") 51 | } 52 | sdk.API_PATH = GlobalConfig.URL + "/api" 53 | 54 | if GlobalConfig.Verbose { 55 | fmt.Printf("Config:\n%#v\n\n", GlobalConfig) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (c *Config) convertBlackListedIPs() error { 62 | 63 | for i := range c.BlackListedNetsStr { 64 | 65 | _, net, err := net.ParseCIDR(c.BlackListedNetsStr[i]) 66 | if err != nil { 67 | return err 68 | } 69 | c.BlacklistedNetworks = append(c.BlacklistedNetworks, *net) 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /pkg/go-sdk/scan.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | func Scan(target, port, network string) (Result, error) { 9 | 10 | url := fmt.Sprintf("/scan?target=%s&port=%s&network=%s", target, port, network) 11 | 12 | body, status, err := Get(url) 13 | if err != nil { 14 | return Result{}, err 15 | } 16 | 17 | switch status { 18 | case 200: 19 | var r Result 20 | 21 | if err := json.Unmarshal(body, &r); err != nil { 22 | return r, fmt.Errorf("failed to unmarshal: %s", err) 23 | } 24 | 25 | return r, nil 26 | case 400, 403, 500: 27 | e := Error{} 28 | 29 | if err := json.Unmarshal(body, &e); err != nil { 30 | return Result{}, fmt.Errorf("failed to unmarshal: %s", err) 31 | } 32 | return Result{}, e 33 | default: 34 | return Result{}, fmt.Errorf("unknown status: %d", status) 35 | } 36 | 37 | } 38 | 39 | func PortScan(technique, ip, ports, timeout string) (string, error) { 40 | 41 | url := fmt.Sprintf("/scan/port?technique=%s&ip=%s&port=%s&timeout=%s", technique, ip, ports, timeout) 42 | 43 | body, status, err := Get(url) 44 | if err != nil { 45 | return "", err 46 | } 47 | 48 | switch status { 49 | case 200: 50 | var r ResultStr 51 | 52 | if err := json.Unmarshal(body, &r); err != nil { 53 | return "", fmt.Errorf("failed to unmarshal: %s", err) 54 | } 55 | 56 | return r.Result, nil 57 | case 400, 403: 58 | e := Error{} 59 | 60 | if err := json.Unmarshal(body, &e); err != nil { 61 | return "", fmt.Errorf("failed to unmarshal: %s", err) 62 | } 63 | 64 | return "", e 65 | case 500: 66 | e := Error{} 67 | 68 | if err := json.Unmarshal(body, &e); err != nil { 69 | return "", fmt.Errorf("failed to unmarshal: %s", err) 70 | } 71 | 72 | return "", e 73 | default: 74 | return "", fmt.Errorf("unknown status: %d", status) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/alert.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { warning(1), fatal(2), (255) } AlertLevel; 11 | 12 | enum { 13 | close_notify(0), 14 | unexpected_message(10), 15 | bad_record_mac(20), 16 | decryption_failed(21), 17 | record_overflow(22), 18 | decompression_failure(30), 19 | handshake_failure(40), 20 | bad_certificate(42), 21 | unsupported_certificate(43), 22 | certificate_revoked(44), 23 | certificate_expired(45), 24 | certificate_unknown(46), 25 | illegal_parameter(47), 26 | unknown_ca(48), 27 | access_denied(49), 28 | decode_error(50), 29 | decrypt_error(51), 30 | export_restriction(60), 31 | protocol_version(70), 32 | insufficient_security(71), 33 | internal_error(80), 34 | user_canceled(90), 35 | no_renegotiation(100), 36 | (255) 37 | } AlertDescription; 38 | 39 | struct { 40 | AlertLevel level; 41 | AlertDescription description; 42 | } Alert; 43 | */ 44 | 45 | type alert struct { 46 | Level uint8 47 | Description uint8 48 | } 49 | 50 | func marshalAlert(level, description uint8) []byte { 51 | 52 | return []byte{level, description} 53 | } 54 | 55 | func unmarshalAlert(bytes []byte) (alert, error) { 56 | 57 | var ( 58 | buf = bytebuilder.NewBuffer(bytes) 59 | a alert 60 | ok bool 61 | ) 62 | 63 | if a.Level, ok = buf.ReadUint8(); !ok { 64 | return a, fmt.Errorf("failed to read Level") 65 | } 66 | 67 | if a.Description, ok = buf.ReadUint8(); !ok { 68 | return a, fmt.Errorf("failed to read Description") 69 | } 70 | 71 | if buf.Size() != 0 { 72 | return a, fmt.Errorf("buf is not empty") 73 | } 74 | 75 | return a, nil 76 | } 77 | -------------------------------------------------------------------------------- /pkg/portscan/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elmasy-com/elmasy/pkg/portscan" 8 | ) 9 | 10 | /* 11 | Stealth scan requires root. 12 | This small script is for manual testing. 13 | */ 14 | 15 | func main() { 16 | 17 | ip := "45.33.32.156" //scanme.nmap.org 18 | 19 | ports := make([]int, 0) 20 | for i := 1; i < 1025; i++ { 21 | ports = append(ports, i) 22 | } 23 | 24 | fmt.Printf("-------------------- stealth --------------------\n") 25 | 26 | t := time.Now() 27 | 28 | r, err := portscan.StealthScan(ip, ports, 1*time.Second) 29 | if err != nil { 30 | fmt.Printf("errors: %s\n", err) 31 | } 32 | 33 | fmt.Printf("Time to scan: %s\n", time.Now().Sub(t).String()) 34 | 35 | //fmt.Printf("Result: %#v\n", r) 36 | 37 | fmt.Printf("Open: %d\n", r.Len(portscan.OPEN)) 38 | fmt.Printf("Closed: %#v\n", r.Len(portscan.CLOSED)) 39 | fmt.Printf("Filtered: %#v\n", r.Len(portscan.FILTERED)) 40 | 41 | fmt.Printf("-------------------- connect --------------------\n") 42 | 43 | t = time.Now() 44 | 45 | c, err := portscan.ConnectScan(ip, ports, 1*time.Second) 46 | if err != nil { 47 | fmt.Printf("errors: %s\n", err) 48 | } 49 | 50 | fmt.Printf("Time to scan: %s\n", time.Now().Sub(t).String()) 51 | 52 | fmt.Printf("Open: %d\n", c.Len(portscan.OPEN)) 53 | fmt.Printf("Closed: %d\n", c.Len(portscan.CLOSED)) 54 | fmt.Printf("Filtered: %d\n", c.Len(portscan.FILTERED)) 55 | 56 | fmt.Printf("---------------------- UDP ----------------------\n") 57 | 58 | t = time.Now() 59 | 60 | u, err := portscan.UDPScan(ip, ports, 1*time.Second) 61 | if err != nil { 62 | fmt.Printf("errors: %s\n", err) 63 | } 64 | 65 | fmt.Printf("Time to scan: %s\n", time.Now().Sub(t).String()) 66 | 67 | fmt.Printf("Open: %d\n", u.Len(portscan.OPEN)) 68 | fmt.Printf("Closed: %d\n", u.Len(portscan.CLOSED)) 69 | fmt.Printf("Filtered: %d\n", u.Len(portscan.FILTERED)) 70 | 71 | } 72 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/alert.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { warning(1), fatal(2), (255) } AlertLevel; 11 | 12 | enum { 13 | close_notify(0), 14 | unexpected_message(10), 15 | bad_record_mac(20), 16 | decryption_failed(21), 17 | record_overflow(22), 18 | decompression_failure(30), 19 | handshake_failure(40), 20 | no_certificate_RESERVED (41), 21 | bad_certificate(42), 22 | unsupported_certificate(43), 23 | certificate_revoked(44), 24 | certificate_expired(45), 25 | certificate_unknown(46), 26 | illegal_parameter(47), 27 | unknown_ca(48), 28 | access_denied(49), 29 | decode_error(50), 30 | decrypt_error(51), 31 | export_restriction_RESERVED(60), 32 | protocol_version(70), 33 | insufficient_security(71), 34 | internal_error(80), 35 | user_canceled(90), 36 | no_renegotiation(100), 37 | (255) 38 | } AlertDescription; 39 | 40 | struct { 41 | AlertLevel level; 42 | AlertDescription description; 43 | } Alert; 44 | */ 45 | 46 | type alert struct { 47 | Level uint8 48 | Description uint8 49 | } 50 | 51 | func marshalAlert(level, description uint8) []byte { 52 | 53 | return []byte{level, description} 54 | } 55 | 56 | func unmarshalAlert(bytes []byte) (alert, error) { 57 | 58 | var ( 59 | buf = bytebuilder.NewBuffer(bytes) 60 | a alert 61 | ok bool 62 | ) 63 | 64 | if a.Level, ok = buf.ReadUint8(); !ok { 65 | return a, fmt.Errorf("failed to read Level") 66 | } 67 | 68 | if a.Description, ok = buf.ReadUint8(); !ok { 69 | return a, fmt.Errorf("failed to read Description") 70 | } 71 | 72 | if buf.Size() != 0 { 73 | return a, fmt.Errorf("buf is not empty") 74 | } 75 | 76 | return a, nil 77 | } 78 | -------------------------------------------------------------------------------- /pkg/go-sdk/sdk_test.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import "testing" 4 | 5 | func init() { 6 | API_PATH = "https://dev.elmasy.com/api" 7 | } 8 | 9 | func TestGetIP(t *testing.T) { 10 | ip, err := GetIP() 11 | if err != nil { 12 | t.Fatalf("FAIL: %s", err) 13 | } 14 | 15 | t.Log(ip) 16 | } 17 | 18 | func TestGetRandomIP(t *testing.T) { 19 | 20 | rand, err := GetRandomIP("4") 21 | if err != nil { 22 | t.Fatalf("FAIL: %s", err) 23 | } 24 | 25 | t.Logf("%s", rand) 26 | } 27 | 28 | func TestGetRandomPort(t *testing.T) { 29 | 30 | port, err := GetRandomPort() 31 | if err != nil { 32 | t.Fatalf("FAIL: %s", err) 33 | } 34 | 35 | t.Logf("%s", port) 36 | } 37 | 38 | func TestDNSLookup(t *testing.T) { 39 | 40 | r, err := DNSLookup("A", "elmasy.com") 41 | if err != nil { 42 | t.Fatalf("FAIL: %s", err) 43 | } 44 | 45 | t.Logf("%v", r) 46 | } 47 | 48 | func TestAnalyzeTLS(t *testing.T) { 49 | 50 | r, err := AnalyzeTLS("tls12", "tcp", "95.216.184.245", "443", "danielgorbe.com") 51 | if err != nil { 52 | t.Fatalf("FAIL: %s", err) 53 | } 54 | 55 | t.Logf("%v", r) 56 | } 57 | 58 | func TestGetCertificate(t *testing.T) { 59 | 60 | c, err := GetCertificate("tcp", "142.132.164.231", "443", "elmasy.com") 61 | if err != nil { 62 | t.Fatalf("FAIL: %s\n", err) 63 | } 64 | 65 | t.Logf("%#v", c) 66 | } 67 | 68 | func TestPortScan(t *testing.T) { 69 | 70 | r, errs := PortScan("connect", "142.132.164.231", "80", "2") 71 | if errs != nil { 72 | t.Fatalf("FAIL: %v", errs) 73 | } 74 | 75 | t.Logf("%v", r) 76 | } 77 | 78 | func TestProbe(t *testing.T) { 79 | 80 | r, err := Probe("tls12", "tcp", "142.132.164.231", "443") 81 | if err != nil { 82 | t.Fatalf("FAIL: %s", err) 83 | } 84 | 85 | t.Logf("%v", r) 86 | } 87 | 88 | func TestScan(t *testing.T) { 89 | 90 | r, err := Scan("elmasy.com", "443", "tcp") 91 | if err != nil { 92 | t.Fatalf("FAIL: %s", err) 93 | } 94 | 95 | t.Logf("%#v", r) 96 | } 97 | -------------------------------------------------------------------------------- /internal/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elmasy-com/elmasy/internal/api/ip" 8 | "github.com/elmasy-com/elmasy/internal/api/protocol/dns" 9 | "github.com/elmasy-com/elmasy/internal/api/protocol/probe" 10 | "github.com/elmasy-com/elmasy/internal/api/protocol/tls" 11 | "github.com/elmasy-com/elmasy/internal/api/protocol/tls/certificate" 12 | randomip "github.com/elmasy-com/elmasy/internal/api/random/ip" 13 | randomport "github.com/elmasy-com/elmasy/internal/api/random/port" 14 | "github.com/elmasy-com/elmasy/internal/api/scan" 15 | "github.com/elmasy-com/elmasy/internal/api/scan/port" 16 | "github.com/elmasy-com/elmasy/internal/config" 17 | 18 | "github.com/gin-gonic/gin" 19 | 20 | ginstatic "github.com/gin-contrib/static" 21 | ) 22 | 23 | func logFormat(param gin.LogFormatterParams) string { 24 | 25 | return fmt.Sprintf("%s - [%s] \"%s %s\" %s %d %s \"%s\"\n%s", 26 | param.ClientIP, 27 | param.TimeStamp.Format(time.RFC1123), 28 | param.Method, 29 | param.Path, 30 | param.Request.Proto, 31 | param.StatusCode, 32 | param.Latency, 33 | param.Request.UserAgent(), 34 | param.ErrorMessage, 35 | ) 36 | } 37 | 38 | func SetupRouter() *gin.Engine { 39 | 40 | gin.DisableConsoleColor() 41 | 42 | if !config.GlobalConfig.Verbose { 43 | gin.SetMode(gin.ReleaseMode) 44 | } 45 | 46 | engine := gin.New() 47 | 48 | engine.Use(gin.Recovery()) 49 | engine.Use(gin.LoggerWithFormatter(logFormat)) 50 | 51 | engine.SetTrustedProxies(config.GlobalConfig.TrustedProxies) 52 | 53 | //engine.StaticFS("/doc", doc.MustFS()) 54 | engine.Use(ginstatic.ServeRoot("/", "./static")) 55 | 56 | api := engine.Group("/api") 57 | { 58 | api.GET("/ip", ip.Get) 59 | api.GET("/random/ip", randomip.Get) 60 | api.GET("/random/port", randomport.Get) 61 | api.GET("/protocol/dns/:type/:name", dns.Get) 62 | api.GET("/protocol/tls", tls.Get) 63 | api.GET("/protocol/tls/certificate", certificate.Get) 64 | api.GET("/protocol/probe", probe.Get) 65 | api.GET("/scan", scan.Get) 66 | api.GET("/scan/port", port.Get) 67 | } 68 | 69 | return engine 70 | } 71 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | help() { 6 | echo "build / pack / deploy / clean / run" 7 | } 8 | 9 | clean() { 10 | if [ -d "build/elmasy" ] 11 | then 12 | rm -rf "build/elmasy" 13 | fi 14 | 15 | if [ -f build/elmasy*.tar ] 16 | then 17 | rm build/elmasy*.tar 18 | fi 19 | 20 | } 21 | 22 | 23 | elmasy-dir() { 24 | 25 | echo "Creating required directories..." 26 | 27 | mkdir -p "build/elmasy/static" 28 | } 29 | 30 | elmasy-bin() { 31 | 32 | elmasy-dir 33 | 34 | echo "Building elmasy..." 35 | 36 | 37 | OUTPUT=$(go build -o build/elmasy/elmasy cmd/elmasy/main.go) 38 | if [ $? != 0 ] 39 | then 40 | echo "Failed to build elmasy!" 41 | echo "$OUTPUT" 42 | exit 1 43 | fi 44 | } 45 | 46 | elmasy-frontend() { 47 | 48 | echo "Building frontend..." 49 | 50 | cd frontend 51 | 52 | OUTPUT=$(npm install 2>&1) 53 | if [ $? != 0 ] 54 | then 55 | echo "Failed to install npm dependencies!" 56 | echo "$OUTPUT" 57 | exit 1 58 | fi 59 | 60 | OUTPUT=$(ng build --prod --output-path=../build/elmasy/static 2>&1) 61 | if [ $? != 0 ] 62 | then 63 | echo "Failed to build elmasy!" 64 | echo "$OUTPUT" 65 | exit 1 66 | fi 67 | 68 | cd .. 69 | } 70 | 71 | elmasy-doc() { 72 | 73 | echo "Copying swagger.yaml..." 74 | cp api/swagger.yaml build/elmasy/static/ 75 | 76 | } 77 | 78 | elmasy-other() { 79 | 80 | echo "Copying other files..." 81 | 82 | cp configs/elmasy.conf build/elmasy/elmasy.conf.example 83 | cp init/elmasy.service build/elmasy/ 84 | } 85 | 86 | 87 | build() { 88 | 89 | clean 90 | 91 | elmasy-bin 92 | elmasy-frontend 93 | elmasy-doc 94 | elmasy-other 95 | } 96 | 97 | pack() { 98 | build 99 | 100 | echo "Creating tar archive..." 101 | cd build 102 | tar -cf elmasy_$(git describe --tags).tar elmasy/ 103 | rm -rf elmasy/ 104 | cd .. 105 | } 106 | 107 | deploy() { 108 | pack 109 | 110 | echo "Deploying..." 111 | bash ignore/scripts/deploy.sh 112 | } 113 | 114 | run() { 115 | build 116 | cd elmasy 117 | ./elmasy 118 | } 119 | 120 | $1 121 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/serverhello.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 8 | ) 9 | 10 | /* 11 | struct { 12 | ProtocolVersion server_version; 13 | Random random; 14 | SessionID session_id; 15 | CipherSuite cipher_suite; 16 | CompressionMethod compression_method; 17 | } ServerHello 18 | */ 19 | 20 | type serverHello struct { 21 | Version []byte 22 | Random random 23 | SessionID []byte 24 | CipherSuite ciphersuite.CipherSuite 25 | CompressionMethod uint8 26 | } 27 | 28 | func unmarshalServerHello(bytes []byte) (serverHello, error) { 29 | 30 | var ( 31 | hello serverHello 32 | ok bool 33 | err error 34 | buf = bytebuilder.NewBuffer(bytes) 35 | ) 36 | 37 | if hello.Version = buf.ReadBytes(2); hello.Version == nil { 38 | return hello, fmt.Errorf("failed to read Version") 39 | } 40 | if err := checkVersion(hello.Version[0], hello.Version[1]); err != nil { 41 | return hello, err 42 | } 43 | 44 | if hello.Random, err = unmarshalRandom(buf.ReadBytes(32)); err != nil { 45 | return hello, fmt.Errorf("failed to read Random: %s", err) 46 | } 47 | 48 | if hello.SessionID, ok = buf.ReadVector(8); !ok { 49 | return hello, fmt.Errorf("failed to read SessionID") 50 | } 51 | 52 | if hello.CipherSuite, err = ciphersuite.Unmarhsal(buf.ReadBytes(2)); err != nil { 53 | return hello, fmt.Errorf("failed to read CipherSuite: %s", err) 54 | } 55 | 56 | if hello.CompressionMethod, ok = buf.ReadUint8(); !ok { 57 | return hello, fmt.Errorf("failed to read CompressionMethod") 58 | } 59 | 60 | return hello, nil 61 | } 62 | 63 | func checkVersion(major, minor byte) error { 64 | 65 | if major == 0x54 && minor == 0x54 { 66 | return fmt.Errorf("unencrypted HTTP response") 67 | } 68 | 69 | if major != 0x03 { 70 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 71 | } 72 | 73 | if minor != 0x00 && minor != 0x01 && minor != 0x02 && minor != 0x03 { 74 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/serverhello.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 8 | ) 9 | 10 | /* 11 | struct { 12 | ProtocolVersion server_version; 13 | Random random; 14 | SessionID session_id; 15 | CipherSuite cipher_suite; 16 | CompressionMethod compression_method; 17 | } ServerHello 18 | */ 19 | 20 | type serverHello struct { 21 | Version []byte 22 | Random random 23 | SessionID []byte 24 | CipherSuite ciphersuite.CipherSuite 25 | CompressionMethod uint8 26 | } 27 | 28 | func unmarshalServerHello(bytes []byte) (serverHello, error) { 29 | 30 | var ( 31 | hello serverHello 32 | ok bool 33 | err error 34 | buf = bytebuilder.NewBuffer(bytes) 35 | ) 36 | 37 | if hello.Version = buf.ReadBytes(2); hello.Version == nil { 38 | return hello, fmt.Errorf("failed to read Version") 39 | } 40 | if err := checkVersion(hello.Version[0], hello.Version[1]); err != nil { 41 | return hello, err 42 | } 43 | 44 | if hello.Random, err = unmarshalRandom(buf.ReadBytes(32)); err != nil { 45 | return hello, fmt.Errorf("failed to read Random: %s", err) 46 | } 47 | 48 | if hello.SessionID, ok = buf.ReadVector(8); !ok { 49 | return hello, fmt.Errorf("failed to read SessionID") 50 | } 51 | 52 | if hello.CipherSuite, err = ciphersuite.Unmarhsal(buf.ReadBytes(2)); err != nil { 53 | return hello, fmt.Errorf("failed to read CipherSuite: %s", err) 54 | } 55 | 56 | if hello.CompressionMethod, ok = buf.ReadUint8(); !ok { 57 | return hello, fmt.Errorf("failed to read CompressionMethod") 58 | } 59 | 60 | return hello, nil 61 | } 62 | 63 | func checkVersion(major, minor byte) error { 64 | 65 | if major == 0x54 && minor == 0x54 { 66 | return fmt.Errorf("unencrypted HTTP response") 67 | } 68 | 69 | if major != 0x03 { 70 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 71 | } 72 | 73 | if minor != 0x00 && minor != 0x01 && minor != 0x02 && minor != 0x03 { 74 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/serverhello.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 8 | ) 9 | 10 | /* 11 | struct { 12 | ProtocolVersion server_version; 13 | Random random; 14 | SessionID session_id; 15 | CipherSuite cipher_suite; 16 | CompressionMethod compression_method; 17 | } ServerHello 18 | */ 19 | 20 | type serverHello struct { 21 | Version []byte 22 | Random random 23 | SessionID []byte 24 | CipherSuite ciphersuite.CipherSuite 25 | CompressionMethod uint8 26 | } 27 | 28 | func unmarshalServerHello(bytes []byte) (serverHello, error) { 29 | 30 | var ( 31 | hello serverHello 32 | ok bool 33 | err error 34 | buf = bytebuilder.NewBuffer(bytes) 35 | ) 36 | 37 | if hello.Version = buf.ReadBytes(2); hello.Version == nil { 38 | return hello, fmt.Errorf("failed to read Version") 39 | } 40 | if err := checkVersion(hello.Version[0], hello.Version[1]); err != nil { 41 | return hello, err 42 | } 43 | 44 | if hello.Random, err = unmarshalRandom(buf.ReadBytes(32)); err != nil { 45 | return hello, fmt.Errorf("failed to read Random: %s", err) 46 | } 47 | 48 | if hello.SessionID, ok = buf.ReadVector(8); !ok { 49 | return hello, fmt.Errorf("failed to read SessionID") 50 | } 51 | 52 | if hello.CipherSuite, err = ciphersuite.Unmarhsal(buf.ReadBytes(2)); err != nil { 53 | return hello, fmt.Errorf("failed to read CipherSuite: %s", err) 54 | } 55 | 56 | if hello.CompressionMethod, ok = buf.ReadUint8(); !ok { 57 | return hello, fmt.Errorf("failed to read CompressionMethod") 58 | } 59 | 60 | return hello, nil 61 | } 62 | 63 | func checkVersion(major, minor byte) error { 64 | 65 | if major == 0x54 && minor == 0x54 { 66 | return fmt.Errorf("unencrypted HTTP response") 67 | } 68 | 69 | if major != 0x03 { 70 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 71 | } 72 | 73 | if minor != 0x00 && minor != 0x01 && minor != 0x02 && minor != 0x03 { 74 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/serverhello.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 8 | ) 9 | 10 | /* 11 | struct { 12 | ProtocolVersion server_version; 13 | Random random; 14 | SessionID session_id; 15 | CipherSuite cipher_suite; 16 | CompressionMethod compression_method; 17 | } ServerHello 18 | */ 19 | 20 | type serverHello struct { 21 | Version []byte 22 | Random random 23 | SessionID []byte 24 | CipherSuite ciphersuite.CipherSuite 25 | CompressionMethod uint8 26 | } 27 | 28 | func unmarshalServerHello(bytes []byte) (serverHello, error) { 29 | 30 | var ( 31 | hello serverHello 32 | ok bool 33 | err error 34 | buf = bytebuilder.NewBuffer(bytes) 35 | ) 36 | 37 | if hello.Version = buf.ReadBytes(2); hello.Version == nil { 38 | return hello, fmt.Errorf("failed to read Version") 39 | } 40 | if err := checkVersion(hello.Version[0], hello.Version[1]); err != nil { 41 | return hello, err 42 | } 43 | 44 | if hello.Random, err = unmarshalRandom(buf.ReadBytes(32)); err != nil { 45 | return hello, fmt.Errorf("failed to read Random: %s", err) 46 | } 47 | 48 | if hello.SessionID, ok = buf.ReadVector(8); !ok { 49 | return hello, fmt.Errorf("failed to read SessionID") 50 | } 51 | 52 | if hello.CipherSuite, err = ciphersuite.Unmarhsal(buf.ReadBytes(2)); err != nil { 53 | return hello, fmt.Errorf("failed to read CipherSuite: %s", err) 54 | } 55 | 56 | if hello.CompressionMethod, ok = buf.ReadUint8(); !ok { 57 | return hello, fmt.Errorf("failed to read CompressionMethod") 58 | } 59 | 60 | return hello, nil 61 | } 62 | 63 | func checkVersion(major, minor byte) error { 64 | 65 | if major == 0x54 && minor == 0x54 { 66 | return fmt.Errorf("unencrypted HTTP response") 67 | } 68 | 69 | if major != 0x03 { 70 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 71 | } 72 | 73 | if minor != 0x00 && minor != 0x01 && minor != 0x02 && minor != 0x03 { 74 | return fmt.Errorf("invalid protocol version: 0x%02x 0x%02x", major, minor) 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /internal/api/protocol/probe/handler.go: -------------------------------------------------------------------------------- 1 | package probe 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/elmasy-com/elmasy/internal/utils" 9 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 10 | "github.com/elmasy-com/elmasy/pkg/protocols/dns" 11 | etls "github.com/elmasy-com/elmasy/pkg/protocols/tls" 12 | "github.com/elmasy-com/identify" 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | func Get(c *gin.Context) { 17 | 18 | network := c.DefaultQuery("network", "tcp") 19 | if network != "tcp" && network != "udp" { 20 | err := fmt.Errorf("Invalid network: %s", network) 21 | c.Error(err) 22 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 23 | return 24 | } 25 | 26 | ip := c.Query("ip") 27 | if ip == "" { 28 | err := fmt.Errorf("ip is empty") 29 | c.Error(err) 30 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 31 | return 32 | } 33 | if !identify.IsValidIP(ip) { 34 | err := fmt.Errorf("Invalid ip: %s", ip) 35 | c.Error(err) 36 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 37 | return 38 | } 39 | 40 | port := c.Query("port") 41 | if port == "" { 42 | err := fmt.Errorf("port is empty") 43 | c.Error(err) 44 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 45 | return 46 | } 47 | if !identify.IsValidPort(port) { 48 | err := fmt.Errorf("Invalid port: %s", port) 49 | c.Error(err) 50 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 51 | return 52 | } 53 | 54 | var ( 55 | supported bool 56 | err error 57 | ) 58 | 59 | switch protocol := c.Query("protocol"); protocol { 60 | case "dns": 61 | supported, err = dns.Probe(network, utils.IPv6BracketAdd(ip), port, 2*time.Second) 62 | case "ssl30", "tls10", "tls11", "tls12", "tls13", "tls": 63 | supported, err = etls.Probe(protocol, network, utils.IPv6BracketAdd(ip), port, 2*time.Second, "") 64 | default: 65 | err = fmt.Errorf("Invalid protocol: %s", protocol) 66 | c.Error(err) 67 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 68 | return 69 | } 70 | 71 | if err != nil { 72 | c.Error(err) 73 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: err.Error()}) 74 | return 75 | } 76 | 77 | c.JSON(http.StatusOK, sdk.ResultBool{Result: supported}) 78 | 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elmasy 2 | 3 | ![Go Report Card](https://goreportcard.com/badge/github.com/elmasy-com/elmasy) 4 | 5 |

6 | 7 | 8 | Elmasy logo 9 | 10 |

11 | 12 | Elmasy *will be* an attack surface analysis tool. 13 | Discover and assess as many as possible publicly accessible assets from a domain name. 14 | 15 | This project has 3 goals: 16 | 17 | - To provide a website for non-professionals/professionals to check the attack surfaces of publicly accessible assets. 18 | - To provide an API for professionals to work with easily. 19 | - The website will use the same API as your service. 20 | - To provide a wide range of libraries for professionals to work with without relying on external services. 21 | - The API will use the same libraries as you. 22 | 23 | As we grow, we will know more protocols, more services and assess/evaluate as fast as the programming language allows. The project is starts from lower layers (like TCP and UDP), basic services (like web servers) and gathering basic information about the servers (like TLS ciphers). 24 | 25 | ## Install 26 | 27 | ```bash 28 | wget https://raw.githubusercontent.com/elmasy-com/elmasy/main/scripts/install.sh 29 | ``` 30 | 31 | ```bash 32 | sudo bash install.sh install 33 | ``` 34 | 35 | ```bash 36 | cp /opt/elmasy/elmasy.conf.example /opt/elmasy/elmasy.conf 37 | ``` 38 | 39 | Edit the config file. 40 | 41 | ## Run 42 | 43 | ### Systemd 44 | 45 | The installer create a service for Elmasy. 46 | 47 | To start: 48 | 49 | ```bash 50 | systemctl start elmasy 51 | ``` 52 | 53 | ### Manual 54 | 55 | If `-config` not specified, Elmasy look for the config file in the current directory. 56 | 57 | ```bash 58 | cd /opt/elmasy && ./elmasy 59 | ``` 60 | 61 | ## Update 62 | 63 | ```bash 64 | sudo bash install.sh updateself && sudo bash install.sh install 65 | ``` 66 | 67 | NOTE: If Elmasy was running before update, the `install` command will restart automatically. 68 | 69 | ## Author 70 | 71 | [System administrator service and Cybersecurity for small and medium-sized businesses in and around Győr.](https://www.gorbe.io/) 72 | -------------------------------------------------------------------------------- /docs/beta/features.md: -------------------------------------------------------------------------------- 1 | # Features of Beta 2 | 3 | Beta version is between `v0.1` and `v1.0`. 4 | 5 | Below is what Elmasy should know until v1.0. 6 | 7 | ## SSL/TLS 8 | 9 | - [x] Versions to check: 10 | - [x] SSL3.0 11 | - [x] TLS1.0 12 | - [x] TLS1.1 13 | - [x] TLS1.2 14 | - [x] TLS1.3 15 | - [x] Check cipher suites 16 | - [x] Check certificate 17 | - [ ] Check TLS extensions 18 | - [ ] StartTLS support 19 | 20 | ## FTP 21 | 22 | - [ ] Check the used software and its version 23 | - [ ] Check anonymous login 24 | - [ ] Check SSL support 25 | - [ ] FTP bounce attack 26 | 27 | ## SSH 28 | 29 | - [ ] Check the used software and its version 30 | - [ ] Key exchange algorithms 31 | - [ ] Server host hey algorithms 32 | - [ ] Encryption algorithms 33 | - [ ] Message authentication code algorithms 34 | - [ ] Compression algorithms 35 | 36 | ## Telnet 37 | 38 | - [ ] Check the existence of Telnet. 39 | 40 | ## SMTP 41 | 42 | - [ ] Check StartTLS support 43 | - [ ] Check SSL support 44 | - [ ] Check for open relay 45 | 46 | ## DNS 47 | 48 | - [ ] Types: 49 | - [x] A 50 | - [x] AAAA 51 | - [x] MX 52 | - [x] TXT 53 | - [ ] PTR 54 | - [ ] CAA 55 | - [ ] ANY 56 | - [ ] AXFR 57 | 58 | ## HTTP(S) 59 | 60 | - [ ] Check the used software and its version 61 | - [ ] Check for forcing HTTPS 62 | - [ ] Security headers: 63 | - [ ] Cache Control 64 | - [ ] Content-Security-Policy (CSP) 65 | - [ ] Strict-Transport-Security (HSTS) 66 | - [ ] Public Key Pinning (HPKP) 67 | - [ ] X-XSS-Protection 68 | - [ ] X-Frame-Options 69 | - [ ] X-Content-Type-Options 70 | - [ ] Feature-Policy 71 | - [ ] Referrer-Policy 72 | - [ ] Permissions-Policy 73 | 74 | ## IMAP 75 | 76 | - [ ] Check TLS support 77 | 78 | ## POP3 79 | 80 | - [ ] Check TLS support 81 | 82 | ## SPF 83 | 84 | - [ ] Check 85 | 86 | ## DKIM 87 | 88 | - [ ] Probe for popular keys (eg.: `google._domainkey`) 89 | 90 | ## DMARC 91 | 92 | - [ ] Check 93 | 94 | ## Port scanning 95 | 96 | - [x] TCP 97 | - [x] `syn` 98 | - [x] `connect()` 99 | - [ ] UDP: probing for known protocols 100 | - [ ] Banner grabbing 101 | 102 | ## Subdomain enumeration 103 | 104 | - [ ] Integrate [Amass](https://github.com/OWASP/Amass) 105 | 106 | ## Database 107 | 108 | - [ ] Store the results for statistics -------------------------------------------------------------------------------- /pkg/go-sdk/types.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | type ResultStr struct { 4 | Result string `json:"result"` 5 | } 6 | 7 | type ResultBool struct { 8 | Result bool `json:"result"` 9 | } 10 | 11 | type ResultStrs struct { 12 | Results []string `json:"results"` 13 | } 14 | 15 | type Error struct { 16 | Err string `json:"error"` 17 | } 18 | 19 | func (e Error) Error() string { 20 | return e.Err 21 | } 22 | 23 | type Cipher struct { 24 | Name string `json:"name"` 25 | Security string `json:"security"` 26 | } 27 | 28 | type PubKey struct { 29 | Algo string `json:"algo"` 30 | Size int `json:"size"` // Bits 31 | Key string `json:"key"` 32 | Exponent int `json:"exponent"` 33 | Modulus string `json:"modulus"` 34 | } 35 | 36 | // Additional is the additional certificates (eg.: intermediate cert) 37 | type Additional struct { 38 | CommonName string `json:"commonName"` 39 | Hash string `json:"hash"` 40 | NotAfter string `json:"notAfter"` 41 | Issuer string `json:"issuer"` 42 | PublicKey PubKey `json:"publicKey"` 43 | SignatureAlgorithm string `json:"signatureAlgorithm"` 44 | } 45 | 46 | // Cert is hold the fields "interesting" part of the certficate chain. 47 | type Cert struct { 48 | CommonName string `json:"commonName"` 49 | Hash string `json:"hash"` // SHA256 50 | AlternativeNames []string `json:"alternativeNames"` 51 | SignatureAlgorithm string `json:"signatureAlgorithm"` 52 | PublicKey PubKey `json:"publicKey"` 53 | SerialNumber string `json:"serialNumber"` 54 | Issuer string `json:"issuer"` 55 | NotBefore string `json:"notBefore"` 56 | NotAfter string `json:"notAfter"` 57 | Verified bool `json:"verified"` 58 | VerifiedError string `json:"verifiedError"` 59 | Chain []Additional `json:"chain"` 60 | } 61 | 62 | type TLSVersion struct { 63 | Version string `json:"version"` 64 | Supported bool `json:"supported"` 65 | Ciphers []Cipher `json:"ciphers"` 66 | } 67 | 68 | type TLS struct { 69 | Versions []TLSVersion `json:"version"` 70 | Cert Cert `json:"cert"` 71 | } 72 | 73 | type Target struct { 74 | IP string `json:"ip"` 75 | TLS TLS `json:"tls"` 76 | } 77 | 78 | type Result struct { 79 | Domain string `json:"domain"` 80 | Targets []Target `json:"targets"` 81 | Errors []string `json:"errors"` 82 | } 83 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/sslplaintext.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | struct { 11 | uint8 major, minor; 12 | } ProtocolVersion; 13 | 14 | enum { 15 | change_cipher_spec(20), alert(21), handshake(22), 16 | application_data(23), (255) 17 | } ContentType; 18 | 19 | struct { 20 | ContentType type; 21 | ProtocolVersion version; 22 | uint16 length; 23 | opaque fragment[SSLPlaintext.length]; 24 | } SSLPlaintext; 25 | */ 26 | 27 | func marshalSSLPlaintext(cType uint8, fragment []byte) []byte { 28 | 29 | buf := bytebuilder.NewEmpty() 30 | 31 | buf.WriteUint8(cType) 32 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 33 | buf.WriteInt(len(fragment), 16) 34 | buf.WriteBytes(fragment...) 35 | 36 | return buf.Bytes() 37 | } 38 | 39 | func unmarshalSSLPlaintext(buf *bytebuilder.Buffer) ([]interface{}, error) { 40 | 41 | var ( 42 | err error 43 | cType uint8 44 | length uint16 45 | ok bool 46 | fragment []byte 47 | handshakes []interface{} 48 | ) 49 | 50 | if cType, ok = buf.ReadUint8(); !ok { 51 | return handshakes, fmt.Errorf("failed to read Length") 52 | } 53 | 54 | if version := buf.ReadBytes(2); version == nil { 55 | return handshakes, fmt.Errorf("failed to read protocol version") 56 | } else if err := checkVersion(version[0], version[1]); err != nil { 57 | return handshakes, err 58 | } 59 | 60 | if length, ok = buf.ReadUint16(); !ok { 61 | return handshakes, fmt.Errorf("failed to read length") 62 | } 63 | 64 | if fragment = buf.ReadBytes(int(length)); fragment == nil { 65 | return handshakes, fmt.Errorf("failed to read fragment") 66 | } 67 | 68 | switch cType { 69 | case 20: 70 | return handshakes, fmt.Errorf("SSLPlaintext type change_cipher_spec is not supported") 71 | case 21: 72 | var handshake interface{} 73 | if handshake, err = unmarshalAlert(fragment); err != nil { 74 | return handshakes, fmt.Errorf("failed to unmarshal Alert: %s", err) 75 | } 76 | handshakes = append(handshakes, handshake) 77 | case 22: 78 | if handshakes, err = unmarshalHandshake(fragment); err != nil { 79 | return handshakes, fmt.Errorf("failed to unmarshal SSLPlaintext: %s", err) 80 | } 81 | case 23: 82 | return handshakes, fmt.Errorf("SSLPlaintext type application_data is not supported") 83 | default: 84 | return handshakes, fmt.Errorf("SSLPlaintext type unknown: %d", cType) 85 | } 86 | 87 | return handshakes, nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/sslplaintext.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | struct { 11 | uint8 major, minor; 12 | } ProtocolVersion; 13 | 14 | enum { 15 | change_cipher_spec(20), alert(21), handshake(22), 16 | application_data(23), (255) 17 | } ContentType; 18 | 19 | struct { 20 | ContentType type; 21 | ProtocolVersion version; 22 | uint16 length; 23 | opaque fragment[SSLPlaintext.length]; 24 | } SSLPlaintext; 25 | */ 26 | 27 | func marshalSSLPlaintext(cType uint8, fragment []byte) []byte { 28 | 29 | buf := bytebuilder.NewEmpty() 30 | 31 | buf.WriteUint8(cType) 32 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 33 | buf.WriteInt(len(fragment), 16) 34 | buf.WriteBytes(fragment...) 35 | 36 | return buf.Bytes() 37 | } 38 | 39 | func unmarshalSSLPlaintext(buf *bytebuilder.Buffer) ([]interface{}, error) { 40 | 41 | var ( 42 | err error 43 | cType uint8 44 | length uint16 45 | ok bool 46 | fragment []byte 47 | handshakes []interface{} 48 | ) 49 | 50 | if cType, ok = buf.ReadUint8(); !ok { 51 | return handshakes, fmt.Errorf("failed to read Length") 52 | } 53 | 54 | if version := buf.ReadBytes(2); version == nil { 55 | return handshakes, fmt.Errorf("failed to read protocol version") 56 | } else if err := checkVersion(version[0], version[1]); err != nil { 57 | return handshakes, err 58 | } 59 | 60 | if length, ok = buf.ReadUint16(); !ok { 61 | return handshakes, fmt.Errorf("failed to read length") 62 | } 63 | 64 | if fragment = buf.ReadBytes(int(length)); fragment == nil { 65 | return handshakes, fmt.Errorf("failed to read fragment") 66 | } 67 | 68 | switch cType { 69 | case 20: 70 | return handshakes, fmt.Errorf("SSLPlaintext type change_cipher_spec is not supported") 71 | case 21: 72 | var handshake interface{} 73 | if handshake, err = unmarshalAlert(fragment); err != nil { 74 | return handshakes, fmt.Errorf("failed to unmarshal Alert: %s", err) 75 | } 76 | handshakes = append(handshakes, handshake) 77 | case 22: 78 | if handshakes, err = unmarshalHandshake(fragment); err != nil { 79 | return handshakes, fmt.Errorf("failed to unmarshal SSLPlaintext: %s", err) 80 | } 81 | case 23: 82 | return handshakes, fmt.Errorf("SSLPlaintext type application_data is not supported") 83 | default: 84 | return handshakes, fmt.Errorf("SSLPlaintext type unknown: %d", cType) 85 | } 86 | 87 | return handshakes, nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/sslplaintext.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | struct { 11 | uint8 major, minor; 12 | } ProtocolVersion; 13 | 14 | enum { 15 | change_cipher_spec(20), alert(21), handshake(22), 16 | application_data(23), (255) 17 | } ContentType; 18 | 19 | struct { 20 | ContentType type; 21 | ProtocolVersion version; 22 | uint16 length; 23 | opaque fragment[SSLPlaintext.length]; 24 | } SSLPlaintext; 25 | */ 26 | 27 | func marshalSSLPlaintext(cType uint8, fragment []byte) []byte { 28 | 29 | buf := bytebuilder.NewEmpty() 30 | 31 | buf.WriteUint8(cType) 32 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 33 | buf.WriteInt(len(fragment), 16) 34 | buf.WriteBytes(fragment...) 35 | 36 | return buf.Bytes() 37 | } 38 | 39 | func unmarshalSSLPlaintext(buf *bytebuilder.Buffer) ([]interface{}, error) { 40 | 41 | var ( 42 | err error 43 | cType uint8 44 | length uint16 45 | ok bool 46 | fragment []byte 47 | handshakes []interface{} 48 | ) 49 | 50 | if cType, ok = buf.ReadUint8(); !ok { 51 | return handshakes, fmt.Errorf("failed to read Length") 52 | } 53 | 54 | if version := buf.ReadBytes(2); version == nil { 55 | return handshakes, fmt.Errorf("failed to read protocol version") 56 | } else if err := checkVersion(version[0], version[1]); err != nil { 57 | return handshakes, err 58 | } 59 | 60 | if length, ok = buf.ReadUint16(); !ok { 61 | return handshakes, fmt.Errorf("failed to read length") 62 | } 63 | 64 | if fragment = buf.ReadBytes(int(length)); fragment == nil { 65 | return handshakes, fmt.Errorf("failed to read fragment") 66 | } 67 | 68 | switch cType { 69 | case 20: 70 | return handshakes, fmt.Errorf("SSLPlaintext type change_cipher_spec is not supported") 71 | case 21: 72 | var handshake interface{} 73 | if handshake, err = unmarshalAlert(fragment); err != nil { 74 | return handshakes, fmt.Errorf("failed to unmarshal Alert: %s", err) 75 | } 76 | handshakes = append(handshakes, handshake) 77 | case 22: 78 | if handshakes, err = unmarshalHandshake(fragment); err != nil { 79 | return handshakes, fmt.Errorf("failed to unmarshal SSLPlaintext: %s", err) 80 | } 81 | case 23: 82 | return handshakes, fmt.Errorf("SSLPlaintext type application_data is not supported") 83 | default: 84 | return handshakes, fmt.Errorf("SSLPlaintext type unknown: %d", cType) 85 | } 86 | 87 | return handshakes, nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/sslplaintext.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | struct { 11 | uint8 major, minor; 12 | } ProtocolVersion; 13 | 14 | enum { 15 | change_cipher_spec(20), alert(21), handshake(22), 16 | application_data(23), (255) 17 | } ContentType; 18 | 19 | struct { 20 | ContentType type; 21 | ProtocolVersion version; 22 | uint16 length; 23 | opaque fragment[SSLPlaintext.length]; 24 | } SSLPlaintext; 25 | */ 26 | 27 | func marshalSSLPlaintext(cType uint8, fragment []byte) []byte { 28 | 29 | buf := bytebuilder.NewEmpty() 30 | 31 | buf.WriteUint8(cType) 32 | buf.WriteBytes(VER_MAJOR, VER_MINOR) 33 | buf.WriteInt(len(fragment), 16) 34 | buf.WriteBytes(fragment...) 35 | 36 | return buf.Bytes() 37 | } 38 | 39 | func unmarshalSSLPlaintext(buf *bytebuilder.Buffer) ([]interface{}, error) { 40 | 41 | var ( 42 | err error 43 | cType uint8 44 | length uint16 45 | ok bool 46 | fragment []byte 47 | handshakes []interface{} 48 | ) 49 | 50 | if cType, ok = buf.ReadUint8(); !ok { 51 | return handshakes, fmt.Errorf("failed to read Length") 52 | } 53 | 54 | if version := buf.ReadBytes(2); version == nil { 55 | return handshakes, fmt.Errorf("failed to read protocol version") 56 | } else if err := checkVersion(version[0], version[1]); err != nil { 57 | return handshakes, err 58 | } 59 | 60 | if length, ok = buf.ReadUint16(); !ok { 61 | return handshakes, fmt.Errorf("failed to read length") 62 | } 63 | 64 | if fragment = buf.ReadBytes(int(length)); fragment == nil { 65 | return handshakes, fmt.Errorf("failed to read fragment") 66 | } 67 | 68 | switch cType { 69 | case 20: 70 | return handshakes, fmt.Errorf("SSLPlaintext type change_cipher_spec is not supported") 71 | case 21: 72 | var handshake interface{} 73 | if handshake, err = unmarshalAlert(fragment); err != nil { 74 | return handshakes, fmt.Errorf("failed to unmarshal Alert: %s", err) 75 | } 76 | handshakes = append(handshakes, handshake) 77 | case 22: 78 | if handshakes, err = unmarshalHandshake(fragment); err != nil { 79 | return handshakes, fmt.Errorf("failed to unmarshal SSLPlaintext: %s", err) 80 | } 81 | case 23: 82 | return handshakes, fmt.Errorf("SSLPlaintext type application_data is not supported") 83 | default: 84 | return handshakes, fmt.Errorf("SSLPlaintext type unknown: %d", cType) 85 | } 86 | 87 | return handshakes, nil 88 | } 89 | -------------------------------------------------------------------------------- /internal/api/scan/handler.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "sync" 7 | 8 | "github.com/elmasy-com/elmasy/internal/utils" 9 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 10 | "github.com/elmasy-com/identify" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func Get(c *gin.Context) { 15 | 16 | var err error 17 | 18 | target := c.Query("target") 19 | if target == "" { 20 | err = fmt.Errorf("Target is missing") 21 | c.Error(err) 22 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | if !identify.IsDomainName(target) && !identify.IsValidIPv4(target) { 26 | err = fmt.Errorf("Invalid target: %s", target) 27 | c.Error(err) 28 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | port := c.DefaultQuery("port", "443") 33 | if !identify.IsValidPort(port) { 34 | err = fmt.Errorf("Invalid port: %s", port) 35 | c.Error(err) 36 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | network := c.DefaultQuery("network", "tcp") 41 | if network != "tcp" && network != "udp" { 42 | err = fmt.Errorf("Invalid network: %s", network) 43 | c.Error(err) 44 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 45 | return 46 | } 47 | 48 | ips := make([]string, 0) 49 | var servername string 50 | 51 | if identify.IsDomainName(target) { 52 | ips, err = utils.Lookup46(target) 53 | if err != nil { 54 | var code int 55 | 56 | if err.Error() == "NXDOMAIN" { 57 | code = http.StatusNotFound 58 | } else { 59 | code = http.StatusInternalServerError 60 | } 61 | 62 | err = fmt.Errorf("Lookup failed: %s", err) 63 | c.Error(err) 64 | c.JSON(code, gin.H{"error": err.Error()}) 65 | return 66 | } 67 | 68 | servername = target 69 | } else { 70 | ips = append(ips, target) 71 | } 72 | 73 | targets := make(chan sdk.Target, len(ips)) 74 | errChan := make(chan error, 20) 75 | var wg sync.WaitGroup 76 | 77 | for i := range ips { 78 | wg.Add(1) 79 | go scanTarget(targets, errChan, &wg, network, ips[i], port, servername) 80 | } 81 | 82 | wg.Wait() 83 | close(targets) 84 | close(errChan) 85 | 86 | result := sdk.Result{Domain: target} 87 | 88 | for e := range errChan { 89 | c.Error(e) 90 | result.Errors = append(result.Errors, e.Error()) 91 | } 92 | 93 | for t := range targets { 94 | 95 | result.Targets = append(result.Targets, t) 96 | 97 | } 98 | 99 | c.JSON(http.StatusOK, result) 100 | 101 | } 102 | -------------------------------------------------------------------------------- /internal/api/protocol/tls/handler.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/elmasy-com/elmasy/internal/utils" 9 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 10 | etls "github.com/elmasy-com/elmasy/pkg/protocols/tls" 11 | "github.com/elmasy-com/identify" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | type Cipher struct { 16 | Name string 17 | Security string 18 | } 19 | 20 | type Result struct { 21 | IP string `json:"ip"` 22 | Version string `json:"version"` 23 | Supported bool `json:"supported"` 24 | Ciphers []Cipher `json:"ciphers"` 25 | } 26 | 27 | func Get(c *gin.Context) { 28 | 29 | version := c.Query("version") 30 | if version == "" { 31 | err := fmt.Errorf("version is empty") 32 | c.Error(err) 33 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 34 | return 35 | } 36 | if version != "ssl30" && version != "tls10" && version != "tls11" && version != "tls12" && version != "tls13" { 37 | err := fmt.Errorf("Invalid version: %s", version) 38 | c.Error(err) 39 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 40 | return 41 | } 42 | 43 | network := c.DefaultQuery("network", "tcp") 44 | if network != "tcp" && network != "udp" { 45 | err := fmt.Errorf("Invalid network: %s", network) 46 | c.Error(err) 47 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 48 | return 49 | } 50 | 51 | ip := c.Query("ip") 52 | if ip == "" { 53 | err := fmt.Errorf("ip is empty") 54 | c.Error(err) 55 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 56 | return 57 | } 58 | if !identify.IsValidIP(ip) { 59 | err := fmt.Errorf("Invalid ip: %s", ip) 60 | c.Error(err) 61 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 62 | return 63 | } 64 | ip = utils.IPv6BracketAdd(ip) 65 | 66 | port := c.Query("port") 67 | if port == "" { 68 | err := fmt.Errorf("port is empty") 69 | c.Error(err) 70 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 71 | return 72 | } 73 | if !identify.IsValidPort(port) { 74 | err := fmt.Errorf("Invalid port: %s", port) 75 | c.Error(err) 76 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 77 | return 78 | } 79 | 80 | servername := c.Query("servername") 81 | 82 | r, err := etls.Scan(version, network, ip, port, 2*time.Second, servername) 83 | if err != nil { 84 | err := fmt.Errorf("Failed to scan: %s", err) 85 | c.Error(err) 86 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: err.Error()}) 87 | return 88 | } 89 | 90 | c.JSON(http.StatusOK, sdk.TLSVersion{Version: version, Supported: r.Supported, Ciphers: convertCiphers(r.Ciphers)}) 91 | } 92 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ciphersuite/ciphersuite.go: -------------------------------------------------------------------------------- 1 | package ciphersuite 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/elmasy-com/bytebuilder" 8 | "github.com/elmasy-com/slices" 9 | ) 10 | 11 | const ( 12 | SSL30 uint16 = 0x0300 13 | TLS10 uint16 = 0x0301 14 | TLS11 uint16 = 0x0302 15 | TLS12 uint16 = 0x0303 16 | TLS13 uint16 = 0x0304 17 | ) 18 | 19 | type CipherSuite struct { 20 | Value []byte 21 | Name string 22 | version []uint16 23 | Security string 24 | } 25 | 26 | // Get returns the known ciphers for version, or nil if the version is invalid. 27 | func Get(version uint16) []CipherSuite { 28 | 29 | var v []CipherSuite 30 | 31 | for i := range CipherSuites { 32 | 33 | if slices.Contain(CipherSuites[i].version, version) { 34 | v = append(v, CipherSuites[i]) 35 | } 36 | } 37 | 38 | return v 39 | } 40 | 41 | // Compare returns returns -1 if a < b, 0 if a == b or 1 if a > b. 42 | func (a CipherSuite) Compare(b CipherSuite) int { 43 | 44 | switch { 45 | case a.Value[0] < b.Value[0]: 46 | return -1 47 | case a.Value[0] > b.Value[0]: 48 | return 1 49 | case a.Value[1] < b.Value[1]: 50 | return -1 51 | case a.Value[1] > b.Value[1]: 52 | return 1 53 | default: 54 | return 0 55 | } 56 | } 57 | 58 | func (c CipherSuite) String() string { 59 | return c.Name 60 | } 61 | 62 | // Marshal marshals ciphers to a byte slice 63 | func Marshal(ciphers []CipherSuite) []byte { 64 | 65 | buf := bytebuilder.NewEmpty() 66 | 67 | for i := range ciphers { 68 | buf.WriteBytes(ciphers[i].Value...) 69 | } 70 | 71 | return buf.Bytes() 72 | } 73 | 74 | // Unmarshal returns a known CipherSuite from CipherSuites based on bytes. 75 | func Unmarhsal(bytes []byte) (CipherSuite, error) { 76 | 77 | if bytes == nil { 78 | return CipherSuite{}, fmt.Errorf("bytes is nil") 79 | } 80 | if len(bytes) != 2 { 81 | return CipherSuite{}, fmt.Errorf("invalid length of bytes: %d", len(bytes)) 82 | } 83 | 84 | for i := range CipherSuites { 85 | if CipherSuites[i].Value[0] == bytes[0] && 86 | CipherSuites[i].Value[1] == bytes[1] { 87 | return CipherSuites[i], nil 88 | } 89 | } 90 | 91 | return CipherSuite{}, fmt.Errorf("unknown bytes: 0x%2.X 0x%2.X", bytes[0], bytes[1]) 92 | } 93 | 94 | func Remove(ciphers []CipherSuite, cipher CipherSuite) []CipherSuite { 95 | 96 | v := make([]CipherSuite, 0) 97 | 98 | for i := range ciphers { 99 | if ciphers[i].Compare(cipher) != 0 { 100 | v = append(v, ciphers[i]) 101 | } 102 | } 103 | 104 | return v 105 | } 106 | 107 | // FindByUint16 returns a pointer to a CipherSuite from ciphers based on cipher. 108 | // If not found, returns nil. 109 | func FindByUint16(ciphers []CipherSuite, cipher uint16) *CipherSuite { 110 | 111 | for i := range ciphers { 112 | if binary.BigEndian.Uint16(ciphers[i].Value) == cipher { 113 | return &ciphers[i] 114 | } 115 | } 116 | 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /internal/api/scan/port/utils.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/elmasy-com/elmasy/internal/config" 11 | "github.com/elmasy-com/elmasy/internal/utils" 12 | "github.com/elmasy-com/identify" 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | type Params struct { 17 | Technique string 18 | IP string 19 | Timeout time.Duration 20 | Port int 21 | } 22 | 23 | // Parse the query params and handle error. 24 | // In case of error, this function set error in the context and also return it. 25 | // So, in the calling function, it is enough to return if err != nil. 26 | func parseQuery(c *gin.Context) (Params, error) { 27 | 28 | params := Params{} 29 | 30 | params.Technique = c.DefaultQuery("technique", "connect") 31 | 32 | if params.Technique != "syn" && 33 | params.Technique != "stealth" && 34 | params.Technique != "connect" && 35 | params.Technique != "udp" { 36 | 37 | err := fmt.Errorf("Invalid technique: %s", params.Technique) 38 | c.Error(err) 39 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 40 | return params, err 41 | } 42 | 43 | params.IP = c.Query("ip") 44 | if params.IP == "" { 45 | err := fmt.Errorf("ip is missing") 46 | c.Error(err) 47 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 48 | return params, err 49 | } 50 | if !identify.IsValidIP(params.IP) { 51 | err := fmt.Errorf("Invalid IP address: %s", params.IP) 52 | c.Error(err) 53 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 54 | return params, err 55 | } 56 | if isIPBlacklisted(params.IP) { 57 | err := fmt.Errorf("Blacklisted IP address: %s", params.IP) 58 | c.Error(err) 59 | c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) 60 | return params, err 61 | } 62 | if identify.IsValidIPv6(params.IP) { 63 | params.IP = utils.IPv6BracketAdd(params.IP) 64 | } 65 | 66 | timeoutQuery := c.DefaultQuery("timeout", "2") 67 | timeoutInt, err := strconv.Atoi(timeoutQuery) 68 | if err != nil || timeoutInt < 0 { 69 | err := fmt.Errorf("Invalid timeout: %s", timeoutQuery) 70 | c.Error(err) 71 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 72 | return params, err 73 | } 74 | params.Timeout = time.Duration(timeoutInt) * time.Second 75 | 76 | portQuery := c.Query("port") 77 | if portQuery == "" { 78 | err := fmt.Errorf("port is missing") 79 | c.Error(err) 80 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 81 | return params, err 82 | } 83 | params.Port, err = strconv.Atoi(portQuery) 84 | if err != nil || !identify.IsValidPort(params.Port) { 85 | err := fmt.Errorf("Invalid port: %s", portQuery) 86 | c.Error(err) 87 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 88 | return params, err 89 | } 90 | 91 | return params, nil 92 | } 93 | 94 | // IsIPBlacklisted decides whether ip is in the blacklisted ip address range. 95 | func isIPBlacklisted(ip string) bool { 96 | 97 | p := net.ParseIP(ip) 98 | if p == nil { 99 | panic("Failed to parse IP in isIPBlacklisted(): " + ip) 100 | } 101 | 102 | for i := range config.GlobalConfig.BlacklistedNetworks { 103 | 104 | if config.GlobalConfig.BlacklistedNetworks[i].Contains(p) { 105 | return true 106 | } 107 | } 108 | 109 | return false 110 | } 111 | -------------------------------------------------------------------------------- /pkg/go-sdk/protocol.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | func DNSLookup(t, n string) ([]string, error) { 9 | 10 | url := fmt.Sprintf("/protocol/dns/%s/%s", t, n) 11 | 12 | body, status, err := Get(url) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | switch status { 18 | case 200: 19 | r := ResultStrs{} 20 | 21 | if err := json.Unmarshal(body, &r); err != nil { 22 | return nil, fmt.Errorf("failed to unmarshal: %s", err) 23 | } 24 | 25 | return r.Results, nil 26 | case 400, 404, 500: 27 | e := Error{} 28 | 29 | if err := json.Unmarshal(body, &e); err != nil { 30 | return nil, fmt.Errorf("failed to unmarshal: %s", err) 31 | } 32 | 33 | return nil, e 34 | default: 35 | return nil, fmt.Errorf("unknown status: %d", status) 36 | } 37 | } 38 | 39 | func AnalyzeTLS(version, network, ip, port, servername string) (TLSVersion, error) { 40 | 41 | url := fmt.Sprintf("/protocol/tls?version=%s&network=%s&ip=%s&port=%s&servername=%s", version, network, ip, port, servername) 42 | 43 | body, status, err := Get(url) 44 | if err != nil { 45 | return TLSVersion{}, err 46 | } 47 | 48 | switch status { 49 | case 200: 50 | var r TLSVersion 51 | 52 | if err := json.Unmarshal(body, &r); err != nil { 53 | return TLSVersion{}, fmt.Errorf("failed to unmarshal: %s", err) 54 | } 55 | 56 | return r, nil 57 | case 400, 500: 58 | e := Error{} 59 | 60 | if err := json.Unmarshal(body, &e); err != nil { 61 | return TLSVersion{}, fmt.Errorf("failed to unmarshal: %s", err) 62 | } 63 | 64 | return TLSVersion{}, e 65 | default: 66 | return TLSVersion{}, fmt.Errorf("unknown status: %d", status) 67 | } 68 | } 69 | 70 | func GetCertificate(network, ip, port, servername string) (Cert, error) { 71 | 72 | url := fmt.Sprintf("/protocol/tls/certificate?network=%s&ip=%s&port=%s&servername=%s", network, ip, port, servername) 73 | 74 | body, status, err := Get(url) 75 | if err != nil { 76 | return Cert{}, err 77 | } 78 | 79 | switch status { 80 | case 200: 81 | var r Cert 82 | 83 | if err := json.Unmarshal(body, &r); err != nil { 84 | return Cert{}, fmt.Errorf("failed to unmarshal: %s", err) 85 | } 86 | 87 | return r, nil 88 | case 400, 500: 89 | e := Error{} 90 | 91 | if err := json.Unmarshal(body, &e); err != nil { 92 | return Cert{}, fmt.Errorf("failed to unmarshal: %s", err) 93 | } 94 | 95 | return Cert{}, e 96 | default: 97 | return Cert{}, fmt.Errorf("unknown status: %d", status) 98 | } 99 | 100 | } 101 | 102 | func Probe(protocol, network, ip, port string) (bool, error) { 103 | 104 | url := fmt.Sprintf("/protocol/probe?protocol=%s&network=%s&ip=%s&port=%s", protocol, network, ip, port) 105 | 106 | body, status, err := Get(url) 107 | if err != nil { 108 | return false, err 109 | } 110 | 111 | switch status { 112 | case 200: 113 | r := ResultBool{} 114 | 115 | if err = json.Unmarshal(body, &r); err != nil { 116 | return false, fmt.Errorf("failed to unmarshal: %s", err) 117 | } 118 | 119 | return r.Result, nil 120 | case 400, 500: 121 | e := Error{} 122 | 123 | if err = json.Unmarshal(body, &e); err != nil { 124 | return false, fmt.Errorf("failed to unmarshal: %s", err) 125 | } 126 | 127 | return false, e 128 | default: 129 | return false, fmt.Errorf("unknown status: %d", status) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 9 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ssl30" 10 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls10" 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls11" 12 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls12" 13 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/tls13" 14 | ) 15 | 16 | type TLS struct { 17 | Supported bool 18 | Certificates []x509.Certificate 19 | DefaultCipher ciphersuite.CipherSuite 20 | Ciphers []ciphersuite.CipherSuite 21 | } 22 | 23 | func Scan(version, network, ip, port string, timeout time.Duration, servername string) (TLS, error) { 24 | switch version { 25 | case "ssl30": 26 | r, err := ssl30.Scan(network, ip, port, timeout) 27 | return TLS(r), err 28 | case "tls10": 29 | r, err := tls10.Scan(network, ip, port, timeout, servername) 30 | return TLS(r), err 31 | case "tls11": 32 | r, err := tls11.Scan(network, ip, port, timeout, servername) 33 | return TLS(r), err 34 | case "tls12": 35 | r, err := tls12.Scan(network, ip, port, timeout, servername) 36 | return TLS(r), err 37 | case "tls13": 38 | r, err := tls13.Scan(network, ip, port, timeout, servername) 39 | return TLS(r), err 40 | default: 41 | return TLS{}, fmt.Errorf("invalid version: %s", version) 42 | } 43 | } 44 | 45 | func Handshake(version, network, ip, port string, timeout time.Duration, servername string) (TLS, error) { 46 | 47 | switch version { 48 | case "ssl30": 49 | r, err := ssl30.Handshake(network, ip, port, timeout) 50 | return TLS(r), err 51 | case "tls10": 52 | r, err := tls10.Handshake(network, ip, port, timeout, servername) 53 | return TLS(r), err 54 | case "tls11": 55 | r, err := tls11.Handshake(network, ip, port, timeout, servername) 56 | return TLS(r), err 57 | case "tls12": 58 | r, err := tls12.Handshake(network, ip, port, timeout, servername) 59 | return TLS(r), err 60 | case "tls13": 61 | r, err := tls13.Handshake(network, ip, port, timeout, servername) 62 | return TLS(r), err 63 | default: 64 | return TLS{}, fmt.Errorf("invalid version: %s", version) 65 | } 66 | } 67 | 68 | func Probe(version, network, ip, port string, timeout time.Duration, servername string) (bool, error) { 69 | 70 | versions := []string{"tls12", "tls11", "tls13", "tls10", "ssl30"} 71 | 72 | switch version { 73 | case "ssl30": 74 | r, err := ssl30.Probe(network, ip, port, timeout) 75 | return r, err 76 | case "tls10": 77 | r, err := tls10.Probe(network, ip, port, timeout, servername) 78 | return r, err 79 | case "tls11": 80 | r, err := tls11.Probe(network, ip, port, timeout, servername) 81 | return r, err 82 | case "tls12": 83 | r, err := tls12.Probe(network, ip, port, timeout, servername) 84 | return r, err 85 | case "tls13": 86 | r, err := tls13.Probe(network, ip, port, timeout, servername) 87 | return r, err 88 | case "tls": 89 | 90 | for i := range versions { 91 | supported, err := Probe(versions[i], network, ip, port, timeout, servername) 92 | if err != nil { 93 | return false, err 94 | } 95 | 96 | if supported { 97 | return true, nil 98 | } 99 | } 100 | 101 | return false, nil 102 | default: 103 | return false, fmt.Errorf("invalid version: %s", version) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/handshake.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | hello_request(0), client_hello(1), server_hello(2), 12 | certificate(11), server_key_exchange (12), 13 | certificate_request(13), server_hello_done(14), 14 | certificate_verify(15), client_key_exchange(16), 15 | finished(20), (255) 16 | } HandshakeType; 17 | 18 | struct { 19 | HandshakeType msg_type; // handshake type 20 | uint24 length; // bytes in message 21 | select (HandshakeType) { 22 | case hello_request: HelloRequest; 23 | case client_hello: ClientHello; 24 | case server_hello: ServerHello; 25 | case certificate: Certificate; 26 | case server_key_exchange: ServerKeyExchange; 27 | case certificate_request: CertificateRequest; 28 | case server_hello_done: ServerHelloDone; 29 | case certificate_verify: CertificateVerify; 30 | case client_key_exchange: ClientKeyExchange; 31 | case finished: Finished; 32 | } body; 33 | } Handshake; 34 | */ 35 | 36 | func marshalHandshake(msgType uint8, body []byte) []byte { 37 | 38 | buf := bytebuilder.NewEmpty() 39 | 40 | buf.WriteUint8(msgType) 41 | buf.WriteInt(len(body), 24) 42 | buf.WriteBytes(body...) 43 | 44 | return buf.Bytes() 45 | } 46 | 47 | // Because SSLPlaintext can contain multiple handshake, this function returns a slice. 48 | // Iterates over and over on bytes, until every handshake is read or any error occur. 49 | func unmarshalHandshake(bytes []byte) ([]interface{}, error) { 50 | 51 | var ( 52 | buf = bytebuilder.NewBuffer(bytes) 53 | msgType uint8 54 | length uint32 55 | ok bool 56 | body []byte 57 | messages []interface{} 58 | err error 59 | ) 60 | 61 | for !buf.Empty() { 62 | 63 | var message interface{} 64 | 65 | if msgType, ok = buf.ReadUint8(); !ok { 66 | return messages, fmt.Errorf("failed to read msgType") 67 | } 68 | 69 | if length, ok = buf.ReadUint24(); !ok { 70 | return messages, fmt.Errorf("failed to read length") 71 | } 72 | 73 | if body = buf.ReadBytes(int(length)); body == nil { 74 | return messages, fmt.Errorf("failed to read body") 75 | } 76 | 77 | switch msgType { 78 | case 0: 79 | return messages, fmt.Errorf("handshake type hello_request is not supported") 80 | case 1: 81 | return messages, fmt.Errorf("handshake type client_hello is invalid") 82 | case 2: 83 | if message, err = unmarshalServerHello(body); err != nil { 84 | return messages, fmt.Errorf("failed to unmarshal ServerHello: %s", err) 85 | } 86 | 87 | case 11: 88 | if message, err = unmarhsalCertificate(body); err != nil { 89 | return messages, fmt.Errorf("failed to unmarshal Certificate: %s", err) 90 | } 91 | case 12: 92 | if message, err = unmarshalServerKeyExchange(body); err != nil { 93 | return messages, fmt.Errorf("failed to unmarshal ServerKeyExchange: %s", err) 94 | } 95 | case 13: 96 | if message, err = unmarshalCertificateRequest(body); err != nil { 97 | return messages, fmt.Errorf("failed to unmarshal CertificateRequest: %s", err) 98 | } 99 | case 14: 100 | message = serverHelloDone{} 101 | case 15: 102 | return messages, fmt.Errorf("handshake type certificate_verify is not supported") 103 | case 16: 104 | return messages, fmt.Errorf("handshake type client_key_exchange is not supported") 105 | case 20: 106 | return messages, fmt.Errorf("handshake type finished is not supported") 107 | default: 108 | return messages, fmt.Errorf("unknown Handshake type: %d", msgType) 109 | } 110 | 111 | messages = append(messages, message) 112 | 113 | } 114 | 115 | return messages, nil 116 | } 117 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/handshake.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | hello_request(0), client_hello(1), server_hello(2), 12 | certificate(11), server_key_exchange (12), 13 | certificate_request(13), server_hello_done(14), 14 | certificate_verify(15), client_key_exchange(16), 15 | finished(20), (255) 16 | } HandshakeType; 17 | 18 | struct { 19 | HandshakeType msg_type; // handshake type 20 | uint24 length; // bytes in message 21 | select (HandshakeType) { 22 | case hello_request: HelloRequest; 23 | case client_hello: ClientHello; 24 | case server_hello: ServerHello; 25 | case certificate: Certificate; 26 | case server_key_exchange: ServerKeyExchange; 27 | case certificate_request: CertificateRequest; 28 | case server_hello_done: ServerHelloDone; 29 | case certificate_verify: CertificateVerify; 30 | case client_key_exchange: ClientKeyExchange; 31 | case finished: Finished; 32 | } body; 33 | } Handshake; 34 | */ 35 | 36 | func marshalHandshake(msgType uint8, body []byte) []byte { 37 | 38 | buf := bytebuilder.NewEmpty() 39 | 40 | buf.WriteUint8(msgType) 41 | buf.WriteInt(len(body), 24) 42 | buf.WriteBytes(body...) 43 | 44 | return buf.Bytes() 45 | } 46 | 47 | // Because SSLPlaintext can contain multiple handshake, this function returns a slice. 48 | // Iterates over and over on bytes, until every handshake is read or any error occur. 49 | func unmarshalHandshake(bytes []byte) ([]interface{}, error) { 50 | 51 | var ( 52 | buf = bytebuilder.NewBuffer(bytes) 53 | msgType uint8 54 | length uint32 55 | ok bool 56 | body []byte 57 | messages []interface{} 58 | err error 59 | ) 60 | 61 | for !buf.Empty() { 62 | 63 | var message interface{} 64 | 65 | if msgType, ok = buf.ReadUint8(); !ok { 66 | return messages, fmt.Errorf("failed to read msgType") 67 | } 68 | 69 | if length, ok = buf.ReadUint24(); !ok { 70 | return messages, fmt.Errorf("failed to read length") 71 | } 72 | 73 | if body = buf.ReadBytes(int(length)); body == nil { 74 | return messages, fmt.Errorf("failed to read body") 75 | } 76 | 77 | switch msgType { 78 | case 0: 79 | return messages, fmt.Errorf("handshake type hello_request is not supported") 80 | case 1: 81 | return messages, fmt.Errorf("handshake type client_hello is invalid") 82 | case 2: 83 | if message, err = unmarshalServerHello(body); err != nil { 84 | return messages, fmt.Errorf("failed to unmarshal ServerHello: %s", err) 85 | } 86 | 87 | case 11: 88 | if message, err = unmarhsalCertificate(body); err != nil { 89 | return messages, fmt.Errorf("failed to unmarshal Certificate: %s", err) 90 | } 91 | case 12: 92 | if message, err = unmarshalServerKeyExchange(body); err != nil { 93 | return messages, fmt.Errorf("failed to unmarshal ServerKeyExchange: %s", err) 94 | } 95 | case 13: 96 | if message, err = unmarshalCertificateRequest(body); err != nil { 97 | return messages, fmt.Errorf("failed to unmarshal CertificateRequest: %s", err) 98 | } 99 | case 14: 100 | message = serverHelloDone{} 101 | case 15: 102 | return messages, fmt.Errorf("handshake type certificate_verify is not supported") 103 | case 16: 104 | return messages, fmt.Errorf("handshake type client_key_exchange is not supported") 105 | case 20: 106 | return messages, fmt.Errorf("handshake type finished is not supported") 107 | default: 108 | return messages, fmt.Errorf("unknown Handshake type: %d", msgType) 109 | } 110 | 111 | messages = append(messages, message) 112 | 113 | } 114 | 115 | return messages, nil 116 | } 117 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/handshake.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | hello_request(0), client_hello(1), server_hello(2), 12 | certificate(11), server_key_exchange (12), 13 | certificate_request(13), server_hello_done(14), 14 | certificate_verify(15), client_key_exchange(16), 15 | finished(20), (255) 16 | } HandshakeType; 17 | 18 | struct { 19 | HandshakeType msg_type; // handshake type 20 | uint24 length; // bytes in message 21 | select (HandshakeType) { 22 | case hello_request: HelloRequest; 23 | case client_hello: ClientHello; 24 | case server_hello: ServerHello; 25 | case certificate: Certificate; 26 | case server_key_exchange: ServerKeyExchange; 27 | case certificate_request: CertificateRequest; 28 | case server_hello_done: ServerHelloDone; 29 | case certificate_verify: CertificateVerify; 30 | case client_key_exchange: ClientKeyExchange; 31 | case finished: Finished; 32 | } body; 33 | } Handshake; 34 | */ 35 | 36 | func marshalHandshake(msgType uint8, body []byte) []byte { 37 | 38 | buf := bytebuilder.NewEmpty() 39 | 40 | buf.WriteUint8(msgType) 41 | buf.WriteInt(len(body), 24) 42 | buf.WriteBytes(body...) 43 | 44 | return buf.Bytes() 45 | } 46 | 47 | // Because SSLPlaintext can contain multiple handshake, this function returns a slice. 48 | // Iterates over and over on bytes, until every handshake is read or any error occur. 49 | func unmarshalHandshake(bytes []byte) ([]interface{}, error) { 50 | 51 | var ( 52 | buf = bytebuilder.NewBuffer(bytes) 53 | msgType uint8 54 | length uint32 55 | ok bool 56 | body []byte 57 | messages []interface{} 58 | err error 59 | ) 60 | 61 | for !buf.Empty() { 62 | 63 | var message interface{} 64 | 65 | if msgType, ok = buf.ReadUint8(); !ok { 66 | return messages, fmt.Errorf("failed to read msgType") 67 | } 68 | 69 | if length, ok = buf.ReadUint24(); !ok { 70 | return messages, fmt.Errorf("failed to read length") 71 | } 72 | 73 | if body = buf.ReadBytes(int(length)); body == nil { 74 | return messages, fmt.Errorf("failed to read body") 75 | } 76 | 77 | switch msgType { 78 | case 0: 79 | return messages, fmt.Errorf("handshake type hello_request is not supported") 80 | case 1: 81 | return messages, fmt.Errorf("handshake type client_hello is invalid") 82 | case 2: 83 | if message, err = unmarshalServerHello(body); err != nil { 84 | return messages, fmt.Errorf("failed to unmarshal ServerHello: %s", err) 85 | } 86 | 87 | case 11: 88 | if message, err = unmarhsalCertificate(body); err != nil { 89 | return messages, fmt.Errorf("failed to unmarshal Certificate: %s", err) 90 | } 91 | case 12: 92 | if message, err = unmarshalServerKeyExchange(body); err != nil { 93 | return messages, fmt.Errorf("failed to unmarshal ServerKeyExchange: %s", err) 94 | } 95 | case 13: 96 | if message, err = unmarshalCertificateRequest(body); err != nil { 97 | return messages, fmt.Errorf("failed to unmarshal CertificateRequest: %s", err) 98 | } 99 | case 14: 100 | message = serverHelloDone{} 101 | case 15: 102 | return messages, fmt.Errorf("handshake type certificate_verify is not supported") 103 | case 16: 104 | return messages, fmt.Errorf("handshake type client_key_exchange is not supported") 105 | case 20: 106 | return messages, fmt.Errorf("handshake type finished is not supported") 107 | default: 108 | return messages, fmt.Errorf("unknown Handshake type: %d", msgType) 109 | } 110 | 111 | messages = append(messages, message) 112 | 113 | } 114 | 115 | return messages, nil 116 | } 117 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/handshake.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elmasy-com/bytebuilder" 7 | ) 8 | 9 | /* 10 | enum { 11 | hello_request(0), client_hello(1), server_hello(2), 12 | certificate(11), server_key_exchange (12), 13 | certificate_request(13), server_hello_done(14), 14 | certificate_verify(15), client_key_exchange(16), 15 | finished(20), (255) 16 | } HandshakeType; 17 | 18 | struct { 19 | HandshakeType msg_type; // handshake type 20 | uint24 length; // bytes in message 21 | select (HandshakeType) { 22 | case hello_request: HelloRequest; 23 | case client_hello: ClientHello; 24 | case server_hello: ServerHello; 25 | case certificate: Certificate; 26 | case server_key_exchange: ServerKeyExchange; 27 | case certificate_request: CertificateRequest; 28 | case server_hello_done: ServerHelloDone; 29 | case certificate_verify: CertificateVerify; 30 | case client_key_exchange: ClientKeyExchange; 31 | case finished: Finished; 32 | } body; 33 | } Handshake; 34 | */ 35 | 36 | func marshalHandshake(msgType uint8, body []byte) []byte { 37 | 38 | buf := bytebuilder.NewEmpty() 39 | 40 | buf.WriteUint8(msgType) 41 | buf.WriteInt(len(body), 24) 42 | buf.WriteBytes(body...) 43 | 44 | return buf.Bytes() 45 | } 46 | 47 | // Because SSLPlaintext can contain multiple handshake, this function returns a slice. 48 | // Iterates over and over on bytes, until every handshake is read or any error occur. 49 | func unmarshalHandshake(bytes []byte) ([]interface{}, error) { 50 | 51 | var ( 52 | buf = bytebuilder.NewBuffer(bytes) 53 | msgType uint8 54 | length uint32 55 | ok bool 56 | body []byte 57 | messages []interface{} 58 | err error 59 | ) 60 | 61 | for !buf.Empty() { 62 | 63 | var message interface{} 64 | 65 | if msgType, ok = buf.ReadUint8(); !ok { 66 | return messages, fmt.Errorf("failed to read msgType") 67 | } 68 | 69 | if length, ok = buf.ReadUint24(); !ok { 70 | return messages, fmt.Errorf("failed to read length") 71 | } 72 | 73 | if body = buf.ReadBytes(int(length)); body == nil { 74 | return messages, fmt.Errorf("failed to read body") 75 | } 76 | 77 | switch msgType { 78 | case 0: 79 | return messages, fmt.Errorf("handshake type hello_request is not supported") 80 | case 1: 81 | return messages, fmt.Errorf("handshake type client_hello is invalid") 82 | case 2: 83 | if message, err = unmarshalServerHello(body); err != nil { 84 | return messages, fmt.Errorf("failed to unmarshal ServerHello: %s", err) 85 | } 86 | 87 | case 11: 88 | if message, err = unmarhsalCertificate(body); err != nil { 89 | return messages, fmt.Errorf("failed to unmarshal Certificate: %s", err) 90 | } 91 | case 12: 92 | if message, err = unmarshalServerKeyExchange(body); err != nil { 93 | return messages, fmt.Errorf("failed to unmarshal ServerKeyExchange: %s", err) 94 | } 95 | case 13: 96 | if message, err = unmarshalCertificateRequest(body); err != nil { 97 | return messages, fmt.Errorf("failed to unmarshal CertificateRequest: %s", err) 98 | } 99 | case 14: 100 | message = serverHelloDone{} 101 | case 15: 102 | return messages, fmt.Errorf("handshake type certificate_verify is not supported") 103 | case 16: 104 | return messages, fmt.Errorf("handshake type client_key_exchange is not supported") 105 | case 20: 106 | return messages, fmt.Errorf("handshake type finished is not supported") 107 | default: 108 | return messages, fmt.Errorf("unknown Handshake type: %d", msgType) 109 | } 110 | 111 | messages = append(messages, message) 112 | 113 | } 114 | 115 | return messages, nil 116 | } 117 | -------------------------------------------------------------------------------- /internal/api/scan/scanner.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "sync" 7 | 8 | "github.com/elmasy-com/elmasy/internal/utils" 9 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 10 | ) 11 | 12 | var SupportedTLS = []string{"ssl30", "tls10", "tls11", "tls12", "tls13"} 13 | 14 | func scanTarget(c chan<- sdk.Target, errors chan<- error, wg *sync.WaitGroup, network, ip, port, servername string) { 15 | 16 | defer wg.Done() 17 | 18 | t := sdk.Target{IP: ip} 19 | 20 | switch network { 21 | case "tcp": 22 | state, err := sdk.PortScan("connect", ip, port, "2") 23 | if err != nil { 24 | errors <- fmt.Errorf("%s://%s:%s -> Port scan failed:%s", network, utils.IPv6BracketAdd(ip), port, err) 25 | return 26 | } 27 | 28 | if state != "open" { 29 | errors <- fmt.Errorf("%s://%s:%s -> Port is %s", network, utils.IPv6BracketAdd(ip), port, state) 30 | return 31 | } 32 | 33 | supported, err := sdk.Probe("tls", network, ip, port) 34 | if err != nil { 35 | errors <- fmt.Errorf("%s://%s:%s -> TLS probe failed: %s", network, utils.IPv6BracketAdd(ip), port, err) 36 | return 37 | } 38 | if !supported { 39 | errors <- fmt.Errorf("%s://%s:%s -> TLS not supported", network, utils.IPv6BracketAdd(ip), port) 40 | return 41 | } 42 | 43 | case "udp": 44 | 45 | state, err := sdk.PortScan("udp", ip, port, "2") 46 | if err != nil { 47 | errors <- fmt.Errorf("%s://%s:%s -> Port scan failed:%s", network, utils.IPv6BracketAdd(ip), port, err) 48 | return 49 | } 50 | 51 | if state == "closed" { 52 | errors <- fmt.Errorf("%s://%s:%s -> Port is %s", network, utils.IPv6BracketAdd(ip), port, state) 53 | return 54 | } 55 | 56 | supported, err := sdk.Probe("tls", network, ip, port) 57 | if err != nil { 58 | errors <- fmt.Errorf("%s://%s:%s -> TLS probe failed: %s", network, utils.IPv6BracketAdd(ip), port, err) 59 | return 60 | } 61 | if !supported { 62 | errors <- fmt.Errorf("%s://%s:%s -> TLS not supported", network, utils.IPv6BracketAdd(ip), port) 63 | return 64 | } 65 | 66 | } 67 | 68 | tlsResults := make(chan sdk.TLSVersion, len(SupportedTLS)) 69 | certResult := make(chan sdk.Cert, 1) 70 | var twg sync.WaitGroup 71 | 72 | for i := range SupportedTLS { 73 | twg.Add(1) 74 | go scanTLS(tlsResults, errors, &twg, SupportedTLS[i], network, ip, port, servername) 75 | } 76 | 77 | twg.Add(1) 78 | scanTLSCert(certResult, errors, &twg, network, ip, port, servername) 79 | 80 | twg.Wait() 81 | close(tlsResults) 82 | close(certResult) 83 | 84 | for i := range tlsResults { 85 | 86 | t.TLS.Versions = append(t.TLS.Versions, i) 87 | } 88 | 89 | t.TLS.Cert = <-certResult 90 | 91 | sort.Slice(t.TLS.Versions, func(i, j int) bool { return t.TLS.Versions[i].Version < t.TLS.Versions[j].Version }) 92 | 93 | c <- t 94 | } 95 | 96 | func scanTLS(t chan<- sdk.TLSVersion, errors chan<- error, twg *sync.WaitGroup, version, network, ip, port, servername string) { 97 | defer twg.Done() 98 | 99 | tls, err := sdk.AnalyzeTLS(version, network, ip, port, servername) 100 | 101 | if err != nil { 102 | errors <- fmt.Errorf("%s://%s:%s -> TLS %s: %s", network, utils.IPv6BracketAdd(ip), port, version, err) 103 | return 104 | } 105 | 106 | t <- sdk.TLSVersion{Version: version, Supported: tls.Supported, Ciphers: tls.Ciphers} 107 | } 108 | 109 | func scanTLSCert(t chan<- sdk.Cert, errors chan<- error, twg *sync.WaitGroup, network, ip, port, servername string) { 110 | defer twg.Done() 111 | 112 | cert, err := sdk.GetCertificate(network, ip, port, servername) 113 | if err != nil { 114 | errors <- fmt.Errorf("%s://%s:%s -> TLS Cert: %s", network, utils.IPv6BracketAdd(ip), port, err) 115 | return 116 | } 117 | 118 | if cert.VerifiedError != "" { 119 | errors <- fmt.Errorf("%s://%s:%s -> TLS Cert: %s", network, utils.IPv6BracketAdd(ip), port, cert.VerifiedError) 120 | } 121 | 122 | t <- cert 123 | } 124 | -------------------------------------------------------------------------------- /pkg/portscan/connect.go: -------------------------------------------------------------------------------- 1 | package portscan 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sort" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "golang.org/x/sync/semaphore" 12 | ) 13 | 14 | type connectOpts struct { 15 | address string 16 | timeout time.Duration 17 | result Result 18 | errs []error 19 | lock *semaphore.Weighted 20 | } 21 | 22 | func newConnectOpts(address string, ports []int, timeout time.Duration) (connectOpts, error) { 23 | 24 | var scan connectOpts 25 | 26 | scan.address = address 27 | 28 | scan.timeout = timeout 29 | 30 | for i := range ports { 31 | scan.result = append(scan.result, Port{Port: ports[i], State: FILTERED}) 32 | } 33 | 34 | ulimit, err := GetMaxFD() 35 | if err != nil { 36 | return scan, fmt.Errorf("failed to get max file descriptors: %s", err) 37 | } 38 | scan.lock = semaphore.NewWeighted(int64(ulimit)) 39 | 40 | return scan, nil 41 | 42 | } 43 | 44 | func (c *connectOpts) connect(p *Port, wg *sync.WaitGroup, e chan<- error) { 45 | 46 | target := fmt.Sprintf("%s:%d", c.address, p.Port) 47 | 48 | //fmt.Printf("Scanning %s...\n", target) 49 | 50 | conn, err := net.DialTimeout("tcp", target, c.timeout) 51 | if err != nil { 52 | 53 | switch { 54 | // Possible DROP on firewall 55 | case strings.Contains(err.Error(), "i/o timeout"): 56 | p.State = FILTERED 57 | wg.Done() 58 | 59 | // RST (can be ICMP: Destination unreachable (Port unreachable)) 60 | case strings.Contains(err.Error(), "connection refused"): 61 | p.State = CLOSED 62 | wg.Done() 63 | 64 | // ICMP (type: 3/code: 13): Destination unreachable (Communication administratively filtered) 65 | case strings.Contains(err.Error(), "no route to host"): 66 | p.State = FILTERED 67 | wg.Done() 68 | 69 | // ICMP (type: 3/code: 0): Destination unreachable (Network unreachable) 70 | case strings.Contains(err.Error(), "network is unreachable"): 71 | p.State = FILTERED 72 | wg.Done() 73 | 74 | case strings.Contains(err.Error(), "too many open files"): 75 | time.Sleep(c.timeout) 76 | c.connect(p, wg, e) 77 | default: 78 | e <- fmt.Errorf("unknown error: %s", err) 79 | wg.Done() 80 | 81 | } 82 | } else { 83 | p.State = OPEN 84 | conn.Close() 85 | wg.Done() 86 | } 87 | 88 | } 89 | 90 | func connectscan(opts connectOpts) (Result, []error) { 91 | 92 | var errs []error 93 | ec := make(chan error, len(opts.result)) 94 | var wg sync.WaitGroup 95 | 96 | for i := range opts.result { 97 | 98 | wg.Add(1) 99 | go opts.connect(&opts.result[i], &wg, ec) 100 | } 101 | 102 | wg.Wait() 103 | close(ec) 104 | 105 | for e := range ec { 106 | errs = append(errs, e) 107 | } 108 | 109 | return opts.result, errs 110 | } 111 | 112 | // ConnectScan uses the basic connect() method to determine if ports are open. 113 | // address can be a hostname, not limited to IP address. 114 | // ConnectScan retries the FILTERED ports for once again. 115 | func ConnectScan(address string, ports []int, timeout time.Duration) (Result, []error) { 116 | 117 | var results Result 118 | 119 | scan1, err := newConnectOpts(address, ports, timeout) 120 | if err != nil { 121 | return nil, []error{err} 122 | } 123 | 124 | scan1r, errs := connectscan(scan1) 125 | if err != nil { 126 | return nil, errs 127 | } 128 | 129 | results = append(results, scan1r.GetPorts(OPEN)...) 130 | results = append(results, scan1r.GetPorts(CLOSED)...) 131 | 132 | if scan1r.Len(FILTERED) > 0 { 133 | scan2, err := newConnectOpts(address, scan1r.GetPortsInt(FILTERED), timeout) 134 | if err != nil { 135 | return nil, []error{err} 136 | } 137 | 138 | scan2r, errs := connectscan(scan2) 139 | if errs != nil { 140 | return nil, errs 141 | } 142 | 143 | results = append(results, scan2r...) 144 | } 145 | 146 | sort.Slice(results, func(i, j int) bool { return results[i].Port < results[j].Port }) 147 | 148 | return results, nil 149 | } 150 | -------------------------------------------------------------------------------- /pkg/portscan/udp.go: -------------------------------------------------------------------------------- 1 | package portscan 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "sort" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "golang.org/x/sync/semaphore" 14 | ) 15 | 16 | type udpOpts struct { 17 | address string 18 | timeout time.Duration 19 | result Result 20 | errs []error 21 | lock *semaphore.Weighted 22 | } 23 | 24 | func newUDPOpts(address string, ports []int, timeout time.Duration) (udpOpts, error) { 25 | 26 | scan := udpOpts{address: address, timeout: timeout} 27 | 28 | for i := range ports { 29 | scan.result = append(scan.result, Port{Port: ports[i], State: FILTERED}) 30 | } 31 | 32 | ulimit, err := GetMaxFD() 33 | if err != nil { 34 | return scan, fmt.Errorf("failed to get max file descriptors: %s", err) 35 | } 36 | // With ulimit/2, scan will bit slower, but dont have to bother with " socket: too many open files" errors. 37 | scan.lock = semaphore.NewWeighted(int64(ulimit) / 2) 38 | 39 | return scan, nil 40 | } 41 | 42 | func (c *udpOpts) connect(p *Port, wg *sync.WaitGroup, e chan<- error) { 43 | 44 | defer c.lock.Release(1) 45 | defer wg.Done() 46 | c.lock.Acquire(context.TODO(), 1) 47 | 48 | target := fmt.Sprintf("%s:%d", c.address, p.Port) 49 | 50 | conn, err := net.DialTimeout("udp", target, c.timeout) 51 | if err != nil { 52 | e <- fmt.Errorf("dial error: %s", err) 53 | return 54 | } 55 | defer conn.Close() 56 | 57 | if err := conn.SetDeadline(time.Now().Add(c.timeout)); err != nil { 58 | e <- fmt.Errorf("failed to set deadline: %s", err) 59 | return 60 | } 61 | 62 | // Write some random string 63 | buf := make([]byte, rand.Intn(64)) 64 | 65 | if _, err = rand.Read(buf); err != nil { 66 | e <- fmt.Errorf("failed to read random: %s", err) 67 | return 68 | } 69 | 70 | _, err = conn.Write([]byte("elmasy.com\r\n")) 71 | if err != nil { 72 | 73 | switch true { 74 | case strings.Contains(err.Error(), "i/o timeout"): 75 | // Port filtered or open, report as FILTERED 76 | return 77 | default: 78 | e <- fmt.Errorf("unknown write error: %s", err) 79 | return 80 | } 81 | } 82 | 83 | // Try to read something 84 | n, err := conn.Read(buf) 85 | if err != nil { 86 | 87 | switch true { 88 | case strings.Contains(err.Error(), "i/o timeout"): 89 | // Port filtered or open, report as FILTERED 90 | return 91 | case strings.Contains(err.Error(), "connection refused"): 92 | // Cleraly closed 93 | p.State = CLOSED 94 | return 95 | default: 96 | e <- fmt.Errorf("unknown write error: %s", err) 97 | return 98 | } 99 | } 100 | 101 | if n > 0 { 102 | // Read more than one byte, clearly OPEN 103 | p.State = OPEN 104 | } 105 | } 106 | 107 | func udpscan(opts udpOpts) (Result, []error) { 108 | 109 | var errs []error 110 | ec := make(chan error, len(opts.result)) 111 | var wg sync.WaitGroup 112 | 113 | for i := range opts.result { 114 | 115 | wg.Add(1) 116 | go opts.connect(&opts.result[i], &wg, ec) 117 | } 118 | 119 | wg.Wait() 120 | close(ec) 121 | 122 | for e := range ec { 123 | errs = append(errs, e) 124 | } 125 | 126 | return opts.result, errs 127 | } 128 | 129 | func UDPScan(address string, ports []int, timeout time.Duration) (Result, []error) { 130 | 131 | var results Result 132 | 133 | scan1, err := newUDPOpts(address, ports, timeout) 134 | if err != nil { 135 | return nil, []error{err} 136 | } 137 | 138 | scan1r, errs := udpscan(scan1) 139 | if err != nil { 140 | return nil, errs 141 | } 142 | 143 | results = append(results, scan1r.GetPorts(OPEN)...) 144 | results = append(results, scan1r.GetPorts(CLOSED)...) 145 | 146 | if scan1r.Len(FILTERED) > 0 { 147 | 148 | scan2, err := newUDPOpts(address, scan1r.GetPortsInt(FILTERED), timeout) 149 | if err != nil { 150 | return nil, []error{err} 151 | } 152 | 153 | scan2r, errs := udpscan(scan2) 154 | if errs != nil { 155 | return nil, errs 156 | } 157 | 158 | results = append(results, scan2r...) 159 | } 160 | 161 | sort.Slice(results, func(i, j int) bool { return results[i].Port < results[j].Port }) 162 | 163 | return results, nil 164 | } 165 | -------------------------------------------------------------------------------- /pkg/protocols/tls/ciphersuite/tools/parser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 12 | ) 13 | 14 | /* 15 | Parse every ciphersuite from ciphersuite.info and print it to stdout formatted as a Go struct. 16 | */ 17 | type CipherInfo struct { 18 | ByteOne string `json:"hex_byte_1"` 19 | ByteTwo string `json:"hex_byte_2"` 20 | Versions []string `json:"tls_version"` 21 | Security string `json:"security"` 22 | } 23 | 24 | type Cipher struct { 25 | Cipher map[string]interface{} 26 | } 27 | 28 | type Ciphers struct { 29 | Ciphers []map[string]CipherInfo `json:"ciphersuites"` 30 | } 31 | 32 | var CipherSuites []ciphersuite.CipherSuite 33 | 34 | func main() { 35 | 36 | resp, err := http.Get("https://ciphersuite.info/api/cs/") 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "Failed to GET ciphersuite.info API: %s\n", err) 39 | os.Exit(1) 40 | } 41 | defer resp.Body.Close() 42 | 43 | body, err := io.ReadAll(resp.Body) 44 | if err != nil { 45 | fmt.Fprintf(os.Stderr, "Failed to read body: %s\n", err) 46 | os.Exit(1) 47 | } 48 | 49 | cs := Ciphers{} 50 | 51 | if err := json.Unmarshal(body, &cs); err != nil { 52 | fmt.Fprintf(os.Stderr, "Failed to unmarshal: %s\n", err) 53 | os.Exit(1) 54 | } 55 | 56 | fmt.Printf("package ciphersuite\n\n") 57 | fmt.Printf("var CipherSuites = []CipherSuite {\n") 58 | 59 | for i := range cs.Ciphers { 60 | printCipherSuites(cs.Ciphers[i]) 61 | } 62 | 63 | // This 2 ciphersuite not exist on ciphersuite.info, print is manually. 64 | fmt.Printf("{[]byte{0x00, 0x1C}, \"SSL_FORTEZZA_KEA_WITH_NULL_SHA\", []uint16{SSL30}, \"weak\"},\n") 65 | fmt.Printf("{[]byte{0x00, 0x1D}, \"SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA\", []uint16{SSL30}, \"weak\"},\n") 66 | 67 | fmt.Printf("}\n") 68 | 69 | } 70 | 71 | func printCipherSuites(m map[string]CipherInfo) { 72 | 73 | for k, v := range m { 74 | 75 | fmt.Printf("{[]byte{%s, %s}, ", v.ByteOne, v.ByteTwo) 76 | fmt.Printf("\"%s\", ", k) 77 | 78 | vers := make([]string, 0) 79 | if isSSL30Cipher(k) { 80 | vers = append(vers, "SSL30") 81 | } 82 | for _, version := range v.Versions { 83 | switch version { 84 | case "TLS1.0": 85 | vers = append(vers, "TLS10") 86 | case "TLS1.1": 87 | vers = append(vers, "TLS11") 88 | case "TLS1.2": 89 | vers = append(vers, "TLS12") 90 | case "TLS1.3": 91 | vers = append(vers, "TLS13") 92 | 93 | } 94 | } 95 | 96 | fmt.Printf("[]uint16{%s}, \"%s\"},\n", strings.Join(vers, ","), v.Security) 97 | 98 | } 99 | } 100 | 101 | var ssl30ciphers = []string{ 102 | 103 | "SSL_FORTEZZA_KEA_WITH_NULL_SHA", 104 | "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA", 105 | 106 | "TLS_NULL_WITH_NULL_NULL", 107 | "TLS_RSA_WITH_NULL_MD5", 108 | "TLS_RSA_WITH_NULL_SHA", 109 | "TLS_RSA_EXPORT_WITH_RC4_40_MD5", 110 | "TLS_RSA_WITH_RC4_128_MD5", 111 | "TLS_RSA_WITH_RC4_128_SHA", 112 | "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 113 | "TLS_RSA_WITH_IDEA_CBC_SHA", 114 | "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", 115 | "TLS_RSA_WITH_DES_CBC_SHA", 116 | "TLS_RSA_WITH_3DES_EDE_CBC_SHA", 117 | "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 118 | "TLS_DH_DSS_WITH_DES_CBC_SHA", 119 | "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 120 | "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 121 | "TLS_DH_RSA_WITH_DES_CBC_SHA", 122 | "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 123 | "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 124 | "TLS_DHE_DSS_WITH_DES_CBC_SHA", 125 | "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 126 | "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 127 | "TLS_DHE_RSA_WITH_DES_CBC_SHA", 128 | "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 129 | "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", 130 | "TLS_DH_anon_WITH_RC4_128_MD5", 131 | "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 132 | "TLS_DH_anon_WITH_DES_CBC_SHA", 133 | "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", 134 | 135 | "TLS_KRB5_WITH_DES_CBC_SHA", 136 | } 137 | 138 | func isSSL30Cipher(name string) bool { 139 | 140 | for i := range ssl30ciphers { 141 | if name == ssl30ciphers[i] { 142 | return true 143 | } 144 | } 145 | return false 146 | } 147 | -------------------------------------------------------------------------------- /internal/api/protocol/tls/certificate/handler.go: -------------------------------------------------------------------------------- 1 | package certificate 2 | 3 | import ( 4 | "crypto/dsa" 5 | "crypto/ecdsa" 6 | "crypto/ed25519" 7 | "crypto/elliptic" 8 | "crypto/rsa" 9 | "fmt" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/elmasy-com/elmasy/internal/utils" 14 | "github.com/elmasy-com/elmasy/pkg/go-sdk" 15 | ecertificate "github.com/elmasy-com/elmasy/pkg/protocols/tls/certificate" 16 | "github.com/elmasy-com/identify" 17 | "github.com/gin-gonic/gin" 18 | ) 19 | 20 | func Get(c *gin.Context) { 21 | 22 | network := c.DefaultQuery("network", "tcp") 23 | if network != "tcp" && network != "udp" { 24 | err := fmt.Errorf("Invalid network: %s", network) 25 | c.Error(err) 26 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 27 | return 28 | } 29 | 30 | ip := c.Query("ip") 31 | if ip == "" { 32 | err := fmt.Errorf("ip is empty") 33 | c.Error(err) 34 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 35 | return 36 | } 37 | if !identify.IsValidIP(ip) { 38 | err := fmt.Errorf("Invalid ip: %s", ip) 39 | c.Error(err) 40 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 41 | return 42 | } 43 | ip = utils.IPv6BracketAdd(ip) 44 | 45 | port := c.Query("port") 46 | if port == "" { 47 | err := fmt.Errorf("port is empty") 48 | c.Error(err) 49 | c.JSON(http.StatusBadRequest, sdk.Error{Err: err.Error()}) 50 | return 51 | } 52 | 53 | servername := c.Query("servername") 54 | 55 | r, err := ecertificate.Scan(network, ip, port, 2*time.Second, servername) 56 | if err != nil { 57 | c.Error(err) 58 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: err.Error()}) 59 | return 60 | } 61 | 62 | cert := sdk.Cert{} 63 | 64 | cert.CommonName = r.CommonName 65 | cert.Hash = fmt.Sprintf("%x", r.Hash) 66 | cert.AlternativeNames = r.AlternativeNames 67 | cert.SignatureAlgorithm = r.SignatureAlgorithm.String() 68 | cert.PublicKey.Algo = r.PublicKey.Algo.String() 69 | 70 | switch v := r.PublicKey.Key.(type) { 71 | case *rsa.PublicKey: 72 | cert.PublicKey.Exponent = v.E 73 | cert.PublicKey.Modulus = fmt.Sprintf("%x", v.N) 74 | cert.PublicKey.Size = v.Size() * 8 75 | case *dsa.PublicKey: 76 | cert.PublicKey.Key = fmt.Sprintf("%x", v.Y) 77 | cert.PublicKey.Size = v.P.BitLen() 78 | case *ecdsa.PublicKey: 79 | cert.PublicKey.Key = fmt.Sprintf("%x", elliptic.Marshal(v.Curve, v.X, v.Y)) 80 | cert.PublicKey.Size = v.Params().BitSize 81 | case *ed25519.PublicKey: 82 | cert.PublicKey.Key = fmt.Sprintf("%x", *v) 83 | cert.PublicKey.Size = len(*v) * 16 84 | default: 85 | err := fmt.Errorf("unknown public key") 86 | c.Error(err) 87 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: err.Error()}) 88 | return 89 | } 90 | 91 | cert.SerialNumber = fmt.Sprintf("%x", r.SerialNumber) 92 | cert.Issuer = r.Issuer 93 | cert.NotBefore = r.NotBefore.Format(time.RFC1123) 94 | cert.NotAfter = r.NotAfter.Format(time.RFC1123) 95 | cert.Verified = r.Verified 96 | if r.VerifiedError != nil { 97 | cert.VerifiedError = r.VerifiedError.Error() 98 | } 99 | 100 | for i := range r.Chain { 101 | a := sdk.Additional{} 102 | a.CommonName = r.Chain[i].CommonName 103 | a.Hash = fmt.Sprintf("%x", r.Chain[i].Hash) 104 | a.NotAfter = r.Chain[i].NotAfter.Format(time.RFC1123) 105 | a.Issuer = r.Chain[i].Issuer 106 | a.PublicKey.Algo = r.Chain[i].PublicKey.Algo.String() 107 | 108 | switch v := r.Chain[i].PublicKey.Key.(type) { 109 | case *rsa.PublicKey: 110 | a.PublicKey.Exponent = v.E 111 | a.PublicKey.Modulus = fmt.Sprintf("%x", v.N) 112 | a.PublicKey.Size = v.Size() * 8 113 | case *dsa.PublicKey: 114 | a.PublicKey.Key = fmt.Sprintf("%x", v.Y) 115 | a.PublicKey.Size = v.P.BitLen() 116 | case *ecdsa.PublicKey: 117 | a.PublicKey.Key = fmt.Sprintf("%x", elliptic.Marshal(v.Curve, v.X, v.Y)) 118 | a.PublicKey.Size = v.Params().BitSize 119 | case *ed25519.PublicKey: 120 | a.PublicKey.Key = fmt.Sprintf("%x", *v) 121 | a.PublicKey.Size = len(*v) * 16 122 | default: 123 | err := fmt.Errorf("unknown public key in chain") 124 | c.Error(err) 125 | c.JSON(http.StatusInternalServerError, sdk.Error{Err: err.Error()}) 126 | return 127 | } 128 | 129 | a.SignatureAlgorithm = r.Chain[i].SignatureAlgorithm.String() 130 | 131 | cert.Chain = append(cert.Chain, a) 132 | } 133 | 134 | c.JSON(http.StatusOK, cert) 135 | } 136 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### 4 | # Install elmasy.tar on a Debian server 5 | ### 6 | 7 | # Remove leftover file, if error occured before 8 | if [ -e elmasy.tar ] 9 | then 10 | rm elmasy.tar 11 | fi 12 | 13 | help() { 14 | echo "Usage: bash $0 install / updateself" 15 | } 16 | 17 | updateself() { 18 | 19 | 20 | OUTPUT=$(wget -O $0 'https://raw.githubusercontent.com/elmasy-com/elmasy/main/scripts/install.sh' 2>&1) 21 | if [ $? != 0 ] 22 | then 23 | echo "Failed to download install.sh!" 24 | echo "$OUTPUT" 25 | exit 1 26 | fi 27 | } 28 | 29 | install() { 30 | 31 | if [ $EUID != "0" ] 32 | then 33 | echo "RUN AS ROOT!" 34 | exit 1 35 | fi 36 | 37 | WAS_ACTIVE="false" 38 | 39 | # Create "elmasy" user, if not created before 40 | if [ $(id -u elmasy > /dev/null 2>&1; echo $?) != "0" ] 41 | then 42 | echo "Creating elmasy user..." 43 | 44 | OUTPUT=$(adduser --no-create-home --gecos '' --disabled-login elmasy 2>&1) 45 | if [ $? != 0 ] 46 | then 47 | echo "Failed to create elmasy user!" 48 | echo "$OUTPUT" 49 | exit 1 50 | fi 51 | fi 52 | 53 | # elmasy.tar always point to the latest release 54 | OUTPUT=$(wget "https://elmasy.com/download/elmasy.tar" 2>&1) 55 | if [ $? != 0 ] 56 | then 57 | echo "Failed to download elmasy.tar!" 58 | echo "$OUTPUT" 59 | exit 1 60 | fi 61 | 62 | OUTPUT=$(wget "https://elmasy.com/download/elmasy.tar.sum" 2>&1) 63 | if [ $? != 0 ] 64 | then 65 | echo "Failed to download elmasy.tar.sum!" 66 | echo "$OUTPUT" 67 | exit 1 68 | fi 69 | 70 | echo "Checking SHA512SUM..." 71 | OUTPUT=$(sha512sum -c elmasy.tar.sum 2>&1) 72 | if [ $? != 0 ] 73 | then 74 | echo "Failed to check SHA512SUM!" 75 | echo "$OUTPUT" 76 | exit 1 77 | fi 78 | 79 | # WSL does not use systemd 80 | if [[ $(systemctl 2>&1) != *"Failed to connect to bus: Host is down"* ]] 81 | then 82 | 83 | # Check if Elmasy installed before, and stop the running service 84 | if [ -e "/lib/systemd/system/elmasy.service" ] 85 | then 86 | 87 | if [[ $(systemctl is-active elmasy.service) == "active" ]] 88 | then 89 | echo "Elmasy is running!" 90 | 91 | WAS_ACTIVE="true" 92 | 93 | systemctl stop elmasy.service 94 | if [ $? != 0 ] 95 | then 96 | echo "Failed to stop Elmasy!" 97 | exit 1 98 | else 99 | echo "Elmasy stopped!" 100 | fi 101 | fi 102 | fi 103 | fi 104 | 105 | if [ -e "/opt/elmasy-old" ] 106 | then 107 | echo "Removing elmasy-old..." 108 | rm -r "/opt/elmasy-old" 109 | fi 110 | 111 | if [ -e /opt/elmasy ] 112 | then 113 | echo "Moving old files to /opt/elmasy-old ..." 114 | mv /opt/elmasy /opt/elmasy-old 115 | fi 116 | 117 | echo "Extracting new files..." 118 | tar -xf elmasy.tar -C /opt 119 | 120 | if [ -e /opt/elmasy-old ] 121 | then 122 | OUTPUT=$(diff /opt/elmasy/elmasy.conf.example /opt/elmasy-old/elmasy.conf) 123 | if [ $? != 0 ] 124 | then 125 | echo "Changes in the new config:" 126 | echo "New < | > old" 127 | echo "$OUTPUT" 128 | fi 129 | 130 | echo "Copy the old config file to its new place..." 131 | cp /opt/elmasy-old/elmasy.conf /opt/elmasy 132 | fi 133 | 134 | echo "Setting executable capabilities to allow raw socket..." 135 | setcap cap_net_admin,cap_net_raw=eip /opt/elmasy/elmasy 136 | 137 | # WSL does not use systemd 138 | if [[ $(systemctl 2>&1) != *"Failed to connect to bus: Host is down"* ]] 139 | then 140 | echo "installing the new service file..." 141 | cp /opt/elmasy/elmasy.service /lib/systemd/system/elmasy.service 142 | systemctl daemon-reload 143 | fi 144 | 145 | echo "Removing leftover files..." 146 | rm elmasy.tar 147 | rm elmasy.tar.sum 148 | 149 | if [[ $WAS_ACTIVE == "true" ]] 150 | then 151 | echo "Elmasy was running, restarting..." 152 | systemctl start elmasy 153 | fi 154 | } 155 | 156 | if [[ $1 == "" ]] 157 | then 158 | help 159 | else 160 | $1 161 | fi -------------------------------------------------------------------------------- /pkg/protocols/tls/ssl30/ssl30.go: -------------------------------------------------------------------------------- 1 | package ssl30 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "github.com/elmasy-com/bytebuilder" 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 12 | ) 13 | 14 | const ( 15 | VER_MAJOR uint8 = 0x03 16 | VER_MINOR uint8 = 0x00 17 | ) 18 | 19 | type SSL30 struct { 20 | Supported bool 21 | Certificates []x509.Certificate 22 | DefaultCipher ciphersuite.CipherSuite 23 | Ciphers []ciphersuite.CipherSuite 24 | } 25 | 26 | func sendClientHello(conn *net.Conn, timeout time.Duration, ciphers []ciphersuite.CipherSuite) error { 27 | 28 | hello := createPacketClientHello(ciphers) 29 | 30 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 31 | return fmt.Errorf("failed to set write deadline: %s", err) 32 | } 33 | 34 | if num, err := (*conn).Write(hello); err != nil { 35 | return fmt.Errorf("failed to write: %s", err) 36 | } else if num != len(hello) { 37 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(hello), num) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func readServerResponse(conn *net.Conn, timeout time.Duration) ([]byte, error) { 44 | 45 | var ( 46 | buf bytebuilder.Buffer 47 | err error 48 | ) 49 | 50 | if err := (*conn).SetReadDeadline(time.Now().Add(timeout)); err != nil { 51 | return []byte{}, fmt.Errorf("failed to set read deadline: %s", err) 52 | } 53 | 54 | if buf, err = bytebuilder.ReadAll(*conn); err != nil { 55 | 56 | if strings.Contains(err.Error(), "i/o timeout") { 57 | // Unresponsive server 58 | err = nil 59 | } else if strings.Contains(err.Error(), "connection reset by peer") && buf.Size() == 7 { 60 | // Some servers send an RST straight after a Alert(Handshake failure) packet *at the first handshake*. 61 | // The alert size (including SSLPLaintext) should be 7 byte. 62 | err = nil 63 | } 64 | } 65 | 66 | return buf.Bytes(), err 67 | } 68 | 69 | func sendClosureALert(conn *net.Conn, timeout time.Duration) error { 70 | 71 | close := createClosureAlert() 72 | 73 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 74 | return fmt.Errorf("failed to set write deadline: %s", err) 75 | } 76 | 77 | if num, err := (*conn).Write(close); err != nil { 78 | return fmt.Errorf("failed to write: %s", err) 79 | } else if num != len(close) { 80 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(close), num) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // Do the handshake and return the response as a byte slice. 87 | func handshake(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite) (SSL30, error) { 88 | 89 | conn, err := net.DialTimeout(network, ip+":"+port, timeout) 90 | if err != nil { 91 | return SSL30{}, fmt.Errorf("failed to connect to %s:%s: %s", ip, port, err) 92 | } 93 | defer conn.Close() 94 | 95 | if err := sendClientHello(&conn, timeout, ciphers); err != nil { 96 | return SSL30{}, fmt.Errorf("failed to send ClientHello: %s", err) 97 | } 98 | 99 | resp, err := readServerResponse(&conn, timeout) 100 | if err != nil { 101 | return SSL30{}, fmt.Errorf("failed to read server response: %s", err) 102 | } 103 | 104 | result, err := unmarshalResponse(resp) 105 | if err != nil { 106 | return result, err 107 | } 108 | 109 | if result.Supported { 110 | if err := sendClosureALert(&conn, timeout); err != nil { 111 | return result, fmt.Errorf("failed to send Closure Alert: %s", err) 112 | } 113 | } 114 | 115 | return result, nil 116 | } 117 | 118 | func getSupportedCiphers(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite) ([]ciphersuite.CipherSuite, error) { 119 | 120 | var ( 121 | supported = make([]ciphersuite.CipherSuite, 0) 122 | ) 123 | 124 | for { 125 | 126 | result, err := handshake(network, ip, port, timeout, ciphers) 127 | if err != nil && !strings.Contains(err.Error(), "connection reset by peer") { 128 | return supported, fmt.Errorf("failed to do handshake: %s", err) 129 | } 130 | 131 | if !result.Supported { 132 | return supported, nil 133 | } 134 | 135 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 136 | supported = append(supported, result.DefaultCipher) 137 | } 138 | 139 | } 140 | 141 | func Scan(network, ip, port string, timeout time.Duration) (SSL30, error) { 142 | 143 | ciphers := ciphersuite.Get(ciphersuite.SSL30) 144 | 145 | result, err := handshake(network, ip, port, timeout, ciphers) 146 | if err != nil { 147 | return result, fmt.Errorf("handshake failed: %s", err) 148 | } 149 | 150 | if !result.Supported { 151 | return result, nil 152 | } 153 | 154 | // Remove the default cipher and test the remaining 155 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 156 | 157 | supported, err := getSupportedCiphers(network, ip, port, timeout, ciphers) 158 | if err != nil { 159 | return result, fmt.Errorf("supported ciphers failed: %s", err) 160 | } 161 | 162 | result.Ciphers = append(result.Ciphers, result.DefaultCipher) 163 | result.Ciphers = append(result.Ciphers, supported...) 164 | 165 | return result, nil 166 | } 167 | 168 | func Handshake(network, ip, port string, timeout time.Duration) (SSL30, error) { 169 | return handshake(network, ip, port, timeout, ciphersuite.Get(ciphersuite.SSL30)) 170 | } 171 | 172 | func Probe(network, ip, port string, timeout time.Duration) (bool, error) { 173 | 174 | r, err := Handshake(network, ip, port, timeout) 175 | 176 | return r.Supported, err 177 | } 178 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls10/tls10.go: -------------------------------------------------------------------------------- 1 | package tls10 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "github.com/elmasy-com/bytebuilder" 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 12 | ) 13 | 14 | const ( 15 | VER_MAJOR uint8 = 0x03 16 | VER_MINOR uint8 = 0x01 17 | ) 18 | 19 | type TLS10 struct { 20 | Supported bool 21 | Certificates []x509.Certificate 22 | DefaultCipher ciphersuite.CipherSuite 23 | Ciphers []ciphersuite.CipherSuite 24 | } 25 | 26 | func sendClientHello(conn *net.Conn, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) error { 27 | 28 | hello := createPacketClientHello(ciphers, servername) 29 | 30 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 31 | return fmt.Errorf("failed to set write deadline: %s", err) 32 | } 33 | 34 | if num, err := (*conn).Write(hello); err != nil { 35 | return fmt.Errorf("failed to write: %s", err) 36 | } else if num != len(hello) { 37 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(hello), num) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func readServerResponse(conn *net.Conn, timeout time.Duration) ([]byte, error) { 44 | 45 | var ( 46 | buf bytebuilder.Buffer 47 | err error 48 | ) 49 | 50 | if err := (*conn).SetReadDeadline(time.Now().Add(timeout)); err != nil { 51 | return []byte{}, fmt.Errorf("failed to set read deadline: %s", err) 52 | } 53 | 54 | if buf, err = bytebuilder.ReadAll(*conn); err != nil { 55 | 56 | if strings.Contains(err.Error(), "i/o timeout") { 57 | // Unresponsive server 58 | err = nil 59 | } else if strings.Contains(err.Error(), "connection reset by peer") && buf.Size() == 7 { 60 | // Some servers send an RST straight after a Alert(Handshake failure) packet *at the first handshake*. 61 | // The alert size (including SSLPLaintext) should be 7 byte. 62 | err = nil 63 | } 64 | } 65 | 66 | return buf.Bytes(), err 67 | } 68 | 69 | func sendClosureALert(conn *net.Conn, timeout time.Duration) error { 70 | 71 | close := createClosureAlert() 72 | 73 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 74 | return fmt.Errorf("failed to set write deadline: %s", err) 75 | } 76 | 77 | if num, err := (*conn).Write(close); err != nil { 78 | return fmt.Errorf("failed to write: %s", err) 79 | } else if num != len(close) { 80 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(close), num) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // Do the handshake and return the response as a byte slice. 87 | func handshake(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) (TLS10, error) { 88 | 89 | conn, err := net.DialTimeout(network, ip+":"+port, timeout) 90 | if err != nil { 91 | return TLS10{}, fmt.Errorf("failed to connect to %s:%s: %s", ip, port, err) 92 | } 93 | defer conn.Close() 94 | 95 | if err := sendClientHello(&conn, timeout, ciphers, servername); err != nil { 96 | return TLS10{}, fmt.Errorf("failed to send ClientHello: %s", err) 97 | } 98 | 99 | resp, err := readServerResponse(&conn, timeout) 100 | if err != nil { 101 | return TLS10{}, fmt.Errorf("failed to read server response: %s", err) 102 | } 103 | 104 | result, err := unmarshalResponse(resp) 105 | if err != nil { 106 | return result, err 107 | } 108 | 109 | if result.Supported { 110 | if err := sendClosureALert(&conn, timeout); err != nil { 111 | return result, fmt.Errorf("failed to send Closure Alert: %s", err) 112 | } 113 | } 114 | 115 | return result, nil 116 | } 117 | 118 | func getSupportedCiphers(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) ([]ciphersuite.CipherSuite, error) { 119 | 120 | supported := make([]ciphersuite.CipherSuite, 0) 121 | 122 | for { 123 | 124 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 125 | if err != nil && !strings.Contains(err.Error(), "connection reset by peer") { 126 | return supported, fmt.Errorf("failed to do handshake: %s", err) 127 | } 128 | 129 | if !result.Supported { 130 | return supported, nil 131 | } 132 | 133 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 134 | 135 | supported = append(supported, result.DefaultCipher) 136 | } 137 | 138 | } 139 | 140 | func Scan(network, ip, port string, timeout time.Duration, servername string) (TLS10, error) { 141 | 142 | ciphers := ciphersuite.Get(ciphersuite.TLS10) 143 | 144 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 145 | if err != nil { 146 | return result, fmt.Errorf("handshake failed: %s", err) 147 | } 148 | 149 | if !result.Supported { 150 | return result, nil 151 | } 152 | 153 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 154 | 155 | supported, err := getSupportedCiphers(network, ip, port, timeout, ciphers, servername) 156 | if err != nil { 157 | return result, fmt.Errorf("supported ciphers failed: %s", err) 158 | } 159 | 160 | result.Ciphers = append(result.Ciphers, result.DefaultCipher) 161 | result.Ciphers = append(result.Ciphers, supported...) 162 | 163 | return result, nil 164 | } 165 | 166 | func Handshake(network, ip, port string, timeout time.Duration, servername string) (TLS10, error) { 167 | return handshake(network, ip, port, timeout, ciphersuite.Get(ciphersuite.TLS10), servername) 168 | } 169 | 170 | func Probe(network, ip, port string, timeout time.Duration, servername string) (bool, error) { 171 | 172 | r, err := Handshake(network, ip, port, timeout, servername) 173 | 174 | return r.Supported, err 175 | } 176 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls11/tls11.go: -------------------------------------------------------------------------------- 1 | package tls11 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "github.com/elmasy-com/bytebuilder" 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 12 | ) 13 | 14 | const ( 15 | VER_MAJOR uint8 = 0x03 16 | VER_MINOR uint8 = 0x02 17 | ) 18 | 19 | type TLS11 struct { 20 | Supported bool 21 | Certificates []x509.Certificate 22 | DefaultCipher ciphersuite.CipherSuite 23 | Ciphers []ciphersuite.CipherSuite 24 | } 25 | 26 | func sendClientHello(conn *net.Conn, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) error { 27 | 28 | hello := createPacketClientHello(ciphers, servername) 29 | 30 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 31 | return fmt.Errorf("failed to set write deadline: %s", err) 32 | } 33 | 34 | if num, err := (*conn).Write(hello); err != nil { 35 | return fmt.Errorf("failed to write: %s", err) 36 | } else if num != len(hello) { 37 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(hello), num) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func readServerResponse(conn *net.Conn, timeout time.Duration) ([]byte, error) { 44 | 45 | var ( 46 | buf bytebuilder.Buffer 47 | err error 48 | ) 49 | 50 | if err := (*conn).SetReadDeadline(time.Now().Add(timeout)); err != nil { 51 | return []byte{}, fmt.Errorf("failed to set read deadline: %s", err) 52 | } 53 | 54 | if buf, err = bytebuilder.ReadAll(*conn); err != nil { 55 | 56 | if strings.Contains(err.Error(), "i/o timeout") { 57 | // Unresponsive server 58 | err = nil 59 | } else if strings.Contains(err.Error(), "connection reset by peer") && buf.Size() == 7 { 60 | // Some servers send an RST straight after a Alert(Handshake failure) packet *at the first handshake*. 61 | // The alert size (including SSLPLaintext) should be 7 byte. 62 | err = nil 63 | } 64 | } 65 | 66 | return buf.Bytes(), err 67 | } 68 | 69 | func sendClosureALert(conn *net.Conn, timeout time.Duration) error { 70 | 71 | close := createClosureAlert() 72 | 73 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 74 | return fmt.Errorf("failed to set write deadline: %s", err) 75 | } 76 | 77 | if num, err := (*conn).Write(close); err != nil { 78 | return fmt.Errorf("failed to write: %s", err) 79 | } else if num != len(close) { 80 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(close), num) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // Do the handshake and return the response as a byte slice. 87 | func handshake(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) (TLS11, error) { 88 | 89 | conn, err := net.DialTimeout(network, ip+":"+port, timeout) 90 | if err != nil { 91 | return TLS11{}, fmt.Errorf("failed to connect to %s:%s: %s", ip, port, err) 92 | } 93 | defer conn.Close() 94 | 95 | if err := sendClientHello(&conn, timeout, ciphers, servername); err != nil { 96 | return TLS11{}, fmt.Errorf("failed to send ClientHello: %s", err) 97 | } 98 | 99 | resp, err := readServerResponse(&conn, timeout) 100 | if err != nil { 101 | return TLS11{}, fmt.Errorf("failed to read server response: %s", err) 102 | } 103 | 104 | result, err := unmarshalResponse(resp) 105 | if err != nil { 106 | return result, err 107 | } 108 | 109 | if result.Supported { 110 | if err := sendClosureALert(&conn, timeout); err != nil { 111 | return result, fmt.Errorf("failed to send Closure Alert: %s", err) 112 | } 113 | } 114 | 115 | return result, nil 116 | } 117 | 118 | func getSupportedCiphers(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) ([]ciphersuite.CipherSuite, error) { 119 | 120 | var ( 121 | supported = make([]ciphersuite.CipherSuite, 0) 122 | ) 123 | 124 | for { 125 | 126 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 127 | if err != nil && !strings.Contains(err.Error(), "connection reset by peer") { 128 | return supported, fmt.Errorf("failed to do handshake: %s", err) 129 | } 130 | 131 | if !result.Supported { 132 | return supported, nil 133 | } 134 | 135 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 136 | supported = append(supported, result.DefaultCipher) 137 | } 138 | 139 | } 140 | 141 | func Scan(network, ip, port string, timeout time.Duration, servername string) (TLS11, error) { 142 | 143 | ciphers := ciphersuite.Get(ciphersuite.TLS11) 144 | 145 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 146 | if err != nil { 147 | return result, fmt.Errorf("handshake failed: %s", err) 148 | } 149 | 150 | if !result.Supported { 151 | return result, nil 152 | } 153 | 154 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 155 | 156 | supported, err := getSupportedCiphers(network, ip, port, timeout, ciphers, servername) 157 | if err != nil { 158 | return result, fmt.Errorf("supported ciphers failed: %s", err) 159 | } 160 | 161 | result.Ciphers = append(result.Ciphers, result.DefaultCipher) 162 | result.Ciphers = append(result.Ciphers, supported...) 163 | 164 | return result, nil 165 | } 166 | 167 | func Handshake(network, ip, port string, timeout time.Duration, servername string) (TLS11, error) { 168 | return handshake(network, ip, port, timeout, ciphersuite.Get(ciphersuite.TLS11), servername) 169 | } 170 | 171 | func Probe(network, ip, port string, timeout time.Duration, servername string) (bool, error) { 172 | 173 | r, err := Handshake(network, ip, port, timeout, servername) 174 | 175 | return r.Supported, err 176 | } 177 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls12/tls12.go: -------------------------------------------------------------------------------- 1 | package tls12 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "github.com/elmasy-com/bytebuilder" 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 12 | ) 13 | 14 | const ( 15 | VER_MAJOR uint8 = 0x03 16 | VER_MINOR uint8 = 0x03 17 | ) 18 | 19 | type TLS12 struct { 20 | Supported bool 21 | Certificates []x509.Certificate 22 | DefaultCipher ciphersuite.CipherSuite 23 | Ciphers []ciphersuite.CipherSuite 24 | } 25 | 26 | func sendClientHello(conn *net.Conn, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) error { 27 | 28 | hello := createPacketClientHello(ciphers, servername) 29 | 30 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 31 | return fmt.Errorf("failed to set write deadline: %s", err) 32 | } 33 | 34 | if num, err := (*conn).Write(hello); err != nil { 35 | return fmt.Errorf("failed to write: %s", err) 36 | } else if num != len(hello) { 37 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(hello), num) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func readServerResponse(conn *net.Conn, timeout time.Duration) ([]byte, error) { 44 | 45 | var ( 46 | buf bytebuilder.Buffer 47 | err error 48 | ) 49 | 50 | if err := (*conn).SetReadDeadline(time.Now().Add(timeout)); err != nil { 51 | return []byte{}, fmt.Errorf("failed to set read deadline: %s", err) 52 | } 53 | 54 | if buf, err = bytebuilder.ReadAll(*conn); err != nil { 55 | 56 | if strings.Contains(err.Error(), "i/o timeout") { 57 | // Unresponsive server 58 | err = nil 59 | } else if strings.Contains(err.Error(), "connection reset by peer") && buf.Size() == 7 { 60 | // Some servers send an RST straight after a Alert(Handshake failure) packet *at the first handshake*. 61 | // The alert size (including SSLPLaintext) should be 7 byte. 62 | err = nil 63 | } 64 | } 65 | 66 | return buf.Bytes(), err 67 | } 68 | 69 | func sendClosureALert(conn *net.Conn, timeout time.Duration) error { 70 | 71 | close := createClosureAlert() 72 | 73 | if err := (*conn).SetWriteDeadline(time.Now().Add(timeout)); err != nil { 74 | return fmt.Errorf("failed to set write deadline: %s", err) 75 | } 76 | 77 | if num, err := (*conn).Write(close); err != nil { 78 | return fmt.Errorf("failed to write: %s", err) 79 | } else if num != len(close) { 80 | return fmt.Errorf("fewer bytes written: want(%d) / actual(%d)", len(close), num) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // Do the handshake and return the response as a byte slice. 87 | func handshake(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) (TLS12, error) { 88 | 89 | conn, err := net.DialTimeout(network, ip+":"+port, timeout) 90 | if err != nil { 91 | return TLS12{}, fmt.Errorf("failed to connect to %s:%s: %s", ip, port, err) 92 | } 93 | defer conn.Close() 94 | 95 | if err := sendClientHello(&conn, timeout, ciphers, servername); err != nil { 96 | return TLS12{}, fmt.Errorf("failed to send ClientHello: %s", err) 97 | } 98 | 99 | resp, err := readServerResponse(&conn, timeout) 100 | if err != nil { 101 | return TLS12{}, fmt.Errorf("failed to read server response: %s", err) 102 | } 103 | 104 | result, err := unmarshalResponse(resp) 105 | if err != nil { 106 | return result, err 107 | } 108 | 109 | if result.Supported { 110 | if err := sendClosureALert(&conn, timeout); err != nil { 111 | return result, fmt.Errorf("failed to send Closure Alert: %s", err) 112 | } 113 | } 114 | 115 | return result, nil 116 | } 117 | 118 | func getSupportedCiphers(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) ([]ciphersuite.CipherSuite, error) { 119 | 120 | var ( 121 | supported = make([]ciphersuite.CipherSuite, 0) 122 | ) 123 | 124 | for { 125 | 126 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 127 | if err != nil && !strings.Contains(err.Error(), "connection reset by peer") { 128 | return supported, fmt.Errorf("failed to do handshake: %s", err) 129 | } 130 | 131 | if !result.Supported { 132 | return supported, nil 133 | } 134 | 135 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 136 | supported = append(supported, result.DefaultCipher) 137 | } 138 | 139 | } 140 | 141 | func Scan(network, ip, port string, timeout time.Duration, servername string) (TLS12, error) { 142 | 143 | ciphers := ciphersuite.Get(ciphersuite.TLS12) 144 | 145 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 146 | if err != nil { 147 | return result, fmt.Errorf("handshake failed: %s", err) 148 | } 149 | 150 | if !result.Supported { 151 | return result, nil 152 | } 153 | 154 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 155 | 156 | supported, err := getSupportedCiphers(network, ip, port, timeout, ciphers, servername) 157 | if err != nil { 158 | return result, fmt.Errorf("supported ciphers failed: %s", err) 159 | } 160 | 161 | result.Ciphers = append(result.Ciphers, result.DefaultCipher) 162 | result.Ciphers = append(result.Ciphers, supported...) 163 | 164 | return result, nil 165 | } 166 | 167 | func Handshake(network, ip, port string, timeout time.Duration, servername string) (TLS12, error) { 168 | return handshake(network, ip, port, timeout, ciphersuite.Get(ciphersuite.TLS12), servername) 169 | } 170 | 171 | func Probe(network, ip, port string, timeout time.Duration, servername string) (bool, error) { 172 | 173 | r, err := Handshake(network, ip, port, timeout, servername) 174 | 175 | return r.Supported, err 176 | } 177 | -------------------------------------------------------------------------------- /pkg/protocols/tls/certificate/certificate.go: -------------------------------------------------------------------------------- 1 | package certificate 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/sha256" 7 | "crypto/x509" 8 | "fmt" 9 | "io" 10 | "math/big" 11 | "net/http" 12 | "net/url" 13 | "strings" 14 | "time" 15 | 16 | etls "github.com/elmasy-com/elmasy/pkg/protocols/tls" 17 | "golang.org/x/crypto/ocsp" 18 | ) 19 | 20 | type PubKey struct { 21 | Algo x509.PublicKeyAlgorithm 22 | Key any // *rsa.PublicKey, *ed25519.PublicKey, ... 23 | } 24 | 25 | // Additional is the additional certificates (eg.: intermediate cert) 26 | type Additional struct { 27 | CommonName string 28 | Hash [32]byte 29 | NotAfter time.Time 30 | Issuer string 31 | PublicKey PubKey 32 | SignatureAlgorithm x509.SignatureAlgorithm 33 | } 34 | 35 | // Cert is hold the fields "interesting" part of the certficate chain. 36 | type Cert struct { 37 | CommonName string 38 | Hash [32]byte // SHA256 39 | AlternativeNames []string 40 | SignatureAlgorithm x509.SignatureAlgorithm 41 | PublicKey PubKey 42 | SerialNumber *big.Int 43 | Issuer string 44 | NotBefore time.Time 45 | NotAfter time.Time 46 | Verified bool 47 | VerifiedError error // This is set if Verified == false 48 | Chain []Additional 49 | } 50 | 51 | // Ordered by usage 52 | var tlsVersions = []string{"tls12", "tls13", "tls11", "tls10", "ssl30"} 53 | 54 | func Grab(network, ip, port string, timeout time.Duration, servername string) ([]x509.Certificate, error) { 55 | 56 | for i := range tlsVersions { 57 | r, err := etls.Handshake(tlsVersions[i], network, ip, port, timeout, servername) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | if r.Supported { 63 | return r.Certificates, nil 64 | } 65 | } 66 | 67 | return nil, fmt.Errorf("TLS not supported") 68 | } 69 | 70 | func verifyOCSP(leaf x509.Certificate, issuer x509.Certificate) error { 71 | 72 | opts := ocsp.RequestOptions{Hash: crypto.SHA1} 73 | 74 | buf, err := ocsp.CreateRequest(&leaf, &issuer, &opts) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | req, err := http.NewRequest(http.MethodPost, leaf.OCSPServer[0], bytes.NewBuffer(buf)) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | url, err := url.Parse(leaf.OCSPServer[0]) 85 | if err != nil { 86 | return err 87 | } 88 | req.Header.Add("Content-Type", "application/ocsp-request") 89 | req.Header.Add("Accept", "application/ocsp-response") 90 | req.Header.Add("host", url.Host) 91 | 92 | client := http.Client{Timeout: 5 * time.Second} 93 | 94 | resp, err := client.Do(req) 95 | if err != nil { 96 | return err 97 | } 98 | defer resp.Body.Close() 99 | 100 | body, err := io.ReadAll(resp.Body) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | ocspResp, err := ocsp.ParseResponseForCert(body, &leaf, &issuer) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | switch ocspResp.Status { 111 | case ocsp.Good: 112 | return nil 113 | case ocsp.Revoked: 114 | return fmt.Errorf("revoked") 115 | case ocsp.Unknown: 116 | return fmt.Errorf("unknown") 117 | default: 118 | return fmt.Errorf("invalid status: %d", ocspResp.Status) 119 | } 120 | } 121 | 122 | func Verify(certs []x509.Certificate, servername string) error { 123 | 124 | if len(certs) < 1 { 125 | return fmt.Errorf("zero certificate given") 126 | } 127 | 128 | opts := x509.VerifyOptions{DNSName: servername} 129 | 130 | if len(certs) > 1 { 131 | pool := x509.NewCertPool() 132 | chain := certs[1:] 133 | for i := range chain { 134 | pool.AddCert(&chain[i]) 135 | } 136 | opts.Intermediates = pool 137 | } 138 | 139 | if _, err := certs[0].Verify(opts); err != nil { 140 | // remove the "x509: " prefix 141 | e := strings.TrimPrefix(err.Error(), "x509: ") 142 | return fmt.Errorf("%s", e) 143 | } 144 | 145 | if len(certs[0].OCSPServer) > 0 && len(certs) >= 2 { 146 | 147 | if err := verifyOCSP(certs[0], certs[1]); err != nil { 148 | return err 149 | } 150 | } 151 | 152 | return nil 153 | } 154 | 155 | // parseLeafCert parse the leaf cert and fill the fields of r (result Cert) 156 | func parseLeafCert(cert x509.Certificate, r *Cert) { 157 | 158 | r.CommonName = cert.Subject.CommonName 159 | 160 | r.Hash = sha256.Sum256(cert.Raw) 161 | 162 | for i := range cert.DNSNames { 163 | r.AlternativeNames = append(r.AlternativeNames, cert.DNSNames[i]) 164 | } 165 | for i := range cert.IPAddresses { 166 | r.AlternativeNames = append(r.AlternativeNames, cert.IPAddresses[i].String()) 167 | } 168 | 169 | r.SignatureAlgorithm = cert.SignatureAlgorithm 170 | r.PublicKey.Algo = cert.PublicKeyAlgorithm 171 | r.PublicKey.Key = cert.PublicKey 172 | 173 | r.SerialNumber = cert.SerialNumber 174 | 175 | r.Issuer = cert.Issuer.CommonName 176 | 177 | r.NotBefore = cert.NotBefore 178 | r.NotAfter = cert.NotAfter 179 | } 180 | 181 | func parseChain(certs []x509.Certificate, r *Cert) { 182 | 183 | for i := range certs { 184 | a := Additional{} 185 | 186 | a.CommonName = certs[i].Subject.CommonName 187 | a.Hash = sha256.Sum256(certs[i].Raw) 188 | a.NotAfter = certs[i].NotAfter 189 | a.Issuer = certs[i].Issuer.CommonName 190 | a.PublicKey.Algo = certs[i].PublicKeyAlgorithm 191 | a.PublicKey.Key = certs[i].PublicKey 192 | a.SignatureAlgorithm = certs[i].SignatureAlgorithm 193 | 194 | r.Chain = append(r.Chain, a) 195 | } 196 | } 197 | 198 | func Scan(network, ip, port string, timeout time.Duration, servername string) (Cert, error) { 199 | 200 | if servername == "" { 201 | return Cert{}, fmt.Errorf("servername is empty") 202 | } 203 | 204 | result := Cert{} 205 | 206 | certs, err := Grab(network, ip, port, timeout, servername) 207 | if err != nil { 208 | return Cert{}, err 209 | } 210 | 211 | if len(certs) == 0 { 212 | return result, fmt.Errorf("no certificate") 213 | } 214 | 215 | if err := Verify(certs, servername); err == nil { 216 | result.Verified = true 217 | } else { 218 | result.VerifiedError = err 219 | } 220 | 221 | parseLeafCert(certs[0], &result) 222 | 223 | if len(certs) > 1 { 224 | parseChain(certs[1:], &result) 225 | } 226 | 227 | return result, nil 228 | } 229 | -------------------------------------------------------------------------------- /pkg/protocols/tls/tls13/tls13.go: -------------------------------------------------------------------------------- 1 | package tls13 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/binary" 6 | "fmt" 7 | "net" 8 | "strings" 9 | "time" 10 | 11 | "github.com/elmasy-com/elmasy/pkg/protocols/tls/ciphersuite" 12 | tls "github.com/refraction-networking/utls" 13 | ) 14 | 15 | type TLS13 struct { 16 | Supported bool 17 | Certificates []x509.Certificate 18 | DefaultCipher ciphersuite.CipherSuite 19 | Ciphers []ciphersuite.CipherSuite 20 | } 21 | 22 | func ciphersToUint16(ciphers []ciphersuite.CipherSuite) []uint16 { 23 | 24 | v := make([]uint16, 0) 25 | 26 | for i := range ciphers { 27 | 28 | v = append(v, binary.BigEndian.Uint16(ciphers[i].Value)) 29 | } 30 | 31 | return v 32 | } 33 | 34 | func handshake(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) (TLS13, error) { 35 | 36 | var ( 37 | conf tls.Config 38 | result TLS13 39 | ) 40 | 41 | if servername == "" { 42 | conf.InsecureSkipVerify = true 43 | } else { 44 | conf.ServerName = servername 45 | } 46 | 47 | dialConn, err := net.DialTimeout(network, ip+":"+port, timeout) 48 | if err != nil { 49 | return result, err 50 | } 51 | uTlsConn := tls.UClient(dialConn, &conf, tls.HelloCustom) 52 | defer uTlsConn.Close() 53 | 54 | if err := uTlsConn.SetDeadline(time.Now().Add(timeout)); err != nil { 55 | return result, err 56 | } 57 | 58 | spec := tls.ClientHelloSpec{ 59 | TLSVersMax: tls.VersionTLS13, 60 | TLSVersMin: tls.VersionTLS10, 61 | CipherSuites: ciphersToUint16(ciphers), 62 | Extensions: []tls.TLSExtension{ 63 | &tls.SupportedCurvesExtension{Curves: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521}}, 64 | &tls.SupportedPointsExtension{SupportedPoints: []byte{0}}, // uncompressed 65 | &tls.SessionTicketExtension{}, 66 | &tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{ 67 | tls.ECDSAWithP256AndSHA256, 68 | tls.ECDSAWithP384AndSHA384, 69 | tls.ECDSAWithP521AndSHA512, 70 | tls.PSSWithSHA256, 71 | tls.PSSWithSHA384, 72 | tls.PSSWithSHA512, 73 | tls.PKCS1WithSHA256, 74 | tls.PKCS1WithSHA384, 75 | tls.PKCS1WithSHA512, 76 | tls.ECDSAWithSHA1, 77 | tls.PKCS1WithSHA1}}, 78 | &tls.KeyShareExtension{KeyShares: []tls.KeyShare{{Group: tls.X25519}}}, 79 | &tls.PSKKeyExchangeModesExtension{Modes: []uint8{1}}, // pskModeDHE 80 | &tls.SupportedVersionsExtension{Versions: []uint16{tls.VersionTLS13}}, 81 | }, 82 | GetSessionID: nil, 83 | } 84 | 85 | if servername != "" { 86 | spec.Extensions = append(spec.Extensions, &tls.SNIExtension{}) 87 | } 88 | 89 | if err := uTlsConn.ApplyPreset(&spec); err != nil { 90 | return result, fmt.Errorf("failed to apply spec: %s", err) 91 | } 92 | 93 | err = uTlsConn.Handshake() 94 | if err != nil { 95 | 96 | switch true { 97 | case strings.Contains(err.Error(), "handshake failure"): 98 | return result, nil 99 | case strings.Contains(err.Error(), "protocol version not supported"): 100 | return result, nil 101 | case strings.Contains(err.Error(), "EOF"): 102 | // Based on the tests, EOF means that no reaction to handshake, a "close notify" or TCP RST. 103 | return result, nil 104 | case strings.Contains(err.Error(), "i/o timeout"): 105 | // Unresponsive server 106 | return result, nil 107 | default: 108 | return result, fmt.Errorf("failed to handshake: %s", err) 109 | } 110 | } 111 | 112 | result.Supported = true 113 | 114 | for i := range uTlsConn.ConnectionState().PeerCertificates { 115 | result.Certificates = append(result.Certificates, *uTlsConn.ConnectionState().PeerCertificates[i]) 116 | } 117 | 118 | if c := ciphersuite.FindByUint16(ciphers, uTlsConn.ConnectionState().CipherSuite); c == nil { 119 | return result, fmt.Errorf("failed to find ciphersuite: %x", uTlsConn.ConnectionState().CipherSuite) 120 | } else { 121 | result.DefaultCipher = *c 122 | result.Ciphers = append(result.Ciphers, *c) 123 | } 124 | 125 | return result, nil 126 | } 127 | 128 | // There are ciphersuites, that uTLS cant handle. 129 | // In this case, iterate over it, one by one. If the error message is "server chose an unconfigured cipher suite", the ciphersuite is supported by the server. 130 | func getUnconfiguredCiphers(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) ([]ciphersuite.CipherSuite, error) { 131 | 132 | supported := make([]ciphersuite.CipherSuite, 0) 133 | 134 | for i := range ciphers { 135 | 136 | _, err := handshake(network, ip, port, timeout, []ciphersuite.CipherSuite{ciphers[i]}, servername) 137 | 138 | if err != nil { 139 | if strings.Contains(err.Error(), "server chose an unconfigured cipher suite") { 140 | supported = append(supported, ciphers[i]) 141 | } else { 142 | return supported, err 143 | } 144 | } 145 | } 146 | 147 | return supported, nil 148 | } 149 | 150 | func getSupportedCiphers(network, ip, port string, timeout time.Duration, ciphers []ciphersuite.CipherSuite, servername string) ([]ciphersuite.CipherSuite, error) { 151 | 152 | var ( 153 | supported = make([]ciphersuite.CipherSuite, 0) 154 | ) 155 | 156 | for { 157 | 158 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 159 | if err != nil { 160 | 161 | if strings.Contains(err.Error(), "server chose an unconfigured cipher suite") { 162 | unconfigured, err := getUnconfiguredCiphers(network, ip, port, timeout, ciphers, servername) 163 | if err != nil { 164 | return supported, fmt.Errorf("failed to get unconfigured ciphers: %s", err) 165 | } 166 | supported = append(supported, unconfigured...) 167 | return supported, nil 168 | } 169 | 170 | return supported, fmt.Errorf("failed to do handshake: %s", err) 171 | } 172 | 173 | if !result.Supported { 174 | return supported, nil 175 | } 176 | 177 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 178 | supported = append(supported, result.DefaultCipher) 179 | } 180 | } 181 | 182 | func Scan(network, ip, port string, timeout time.Duration, servername string) (TLS13, error) { 183 | 184 | ciphers := ciphersuite.Get(ciphersuite.TLS13) 185 | 186 | result, err := handshake(network, ip, port, timeout, ciphers, servername) 187 | if err != nil { 188 | return result, fmt.Errorf("handshake failed: %s", err) 189 | } 190 | 191 | if !result.Supported { 192 | return result, nil 193 | } 194 | 195 | ciphers = ciphersuite.Remove(ciphers, result.DefaultCipher) 196 | 197 | supported, err := getSupportedCiphers(network, ip, port, timeout, ciphers, servername) 198 | if err != nil { 199 | return result, fmt.Errorf("failed to get supported ciphers: %s", err) 200 | } 201 | 202 | result.Ciphers = append(result.Ciphers, supported...) 203 | 204 | return result, nil 205 | 206 | } 207 | 208 | func Handshake(network, ip, port string, timeout time.Duration, servername string) (TLS13, error) { 209 | return handshake(network, ip, port, timeout, ciphersuite.Get(ciphersuite.TLS13), servername) 210 | } 211 | 212 | func Probe(network, ip, port string, timeout time.Duration, servername string) (bool, error) { 213 | 214 | r, err := Handshake(network, ip, port, timeout, servername) 215 | 216 | return r.Supported, err 217 | } 218 | -------------------------------------------------------------------------------- /pkg/randomip/randomip.go: -------------------------------------------------------------------------------- 1 | package randomip 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | "time" 7 | ) 8 | 9 | // ReservedIPv4 is a collection of reserved IPv4 addresses. 10 | var ReservedIPv4 = []net.IPNet{ 11 | {IP: net.IP{0, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}}, // 0.0.0.0/8, "This" network 12 | {IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}}, // 10.0.0.0/8, CLass A private network 13 | {IP: net.IP{100, 64, 0, 0}, Mask: net.IPMask{255, 192, 0, 0}}, // 100.64.0.0/10, Carrier-grade NAT 14 | {IP: net.IP{127, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}}, // 127.0.0.0/8, Loopback 15 | {IP: net.IP{169, 254, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}}, // 169.254.0.0/16, Link local 16 | {IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}}, // 172.16.0.0/12, Class B private network 17 | {IP: net.IP{192, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 0}}, // 192.0.0.0/24, IETF protocol assignments 18 | {IP: net.IP{192, 0, 2, 0}, Mask: net.IPMask{255, 255, 255, 0}}, // 192.0.2.0/24, TEST-NET-1 19 | {IP: net.IP{192, 88, 99, 0}, Mask: net.IPMask{255, 255, 255, 0}}, // 192.88.99.0/24, Reserved, formerly IPv6 to IPv4 20 | {IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}}, // 192.168.0.0/24, Class C private network 21 | {IP: net.IP{198, 18, 0, 0}, Mask: net.IPMask{255, 254, 0, 0}}, // 198.18.0.0/15, Benchmarking 22 | {IP: net.IP{198, 51, 100, 0}, Mask: net.IPMask{255, 255, 255, 0}}, // 198.51.100.0/24, TEST-NET-2 23 | {IP: net.IP{203, 0, 113, 0}, Mask: net.IPMask{255, 255, 255, 0}}, // 203.0.113.0/24, TEST-NET-3 24 | {IP: net.IP{224, 0, 0, 0}, Mask: net.IPMask{240, 0, 0, 0}}, // 224.0.0.0/4, Multicast 25 | {IP: net.IP{233, 252, 0, 0}, Mask: net.IPMask{255, 255, 255, 0}}, // 233.252.0.0/24 , MCAST-TEST-NET 26 | {IP: net.IP{240, 0, 0, 0}, Mask: net.IPMask{240, 0, 0, 0}}, // 240.0.0.0/4, Reserved for future use 27 | {IP: net.IP{255, 255, 255, 255}, Mask: net.IPMask{255, 255, 255, 255}}, // 255.255.255.255/32, Broadcast 28 | } 29 | 30 | // ReservedIPv6 is a collection of reserved IPv6 addresses. 31 | var ReservedIPv6 = []net.IPNet{ 32 | {IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}, // ::/128, Unspecified Address 33 | {IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}, // ::1/128, Loopback Address 34 | {IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0}}, // ::ffff:0:0/96, IPv4-mapped addresses 35 | {IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0}}, // ::ffff:0:0:0/96, IPv4 translated addresses 36 | {IP: net.IP{0, 100, 255, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0}}, // 64:ff9b::/96, IPv4-IPv6 Translat. 37 | {IP: net.IP{0, 100, 255, 155, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // 64:ff9b:1::/48, IPv4-IPv6 Translat. 38 | {IP: net.IP{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}}, // 100::/64, Discard-Only Address Block 39 | {IP: net.IP{32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // 2001::/32, IETF Protocol Assignments 40 | {IP: net.IP{32, 1, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // 2001:20::/28, ORCHIDv2 41 | {IP: net.IP{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // 2001:db8::/32, Documentation 42 | {IP: net.IP{32, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // 2002::/16, 6to4 43 | {IP: net.IP{252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // fc00::/7, Unique-Local 44 | {IP: net.IP{254, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // fe80::/10, Link-Local Unicast 45 | {IP: net.IP{255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // ff00::/8, Multicast 46 | } 47 | 48 | // IsReservedIPv4 checks if the given IP address is reserved. 49 | func IsReservedIPv4(ip net.IP) bool { 50 | for i := range ReservedIPv4 { 51 | if ReservedIPv4[i].Contains(ip) { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | // IsReservedIPv6 checks if the given IP address is reserved. 59 | func IsReservedIPv6(ip net.IP) bool { 60 | for i := range ReservedIPv6 { 61 | if ReservedIPv6[i].Contains(ip) { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | // GetRandomIPv4 is return a random IPv4 address. 69 | // The returned IP *can be* a reserved address. 70 | func GetRandomIPv4() net.IP { 71 | 72 | src := rand.NewSource(time.Now().UnixNano()) 73 | rnd := rand.New(src) 74 | 75 | bytes := make([]byte, 4) 76 | 77 | rnd.Read(bytes) 78 | 79 | return net.IP{bytes[0], bytes[1], bytes[2], bytes[3]} 80 | } 81 | 82 | // GetRandomIPv6 is return a random IPv6 address. 83 | // The returned IP *can be* a reserved address. 84 | func GetRandomIPv6() net.IP { 85 | 86 | src := rand.NewSource(time.Now().UnixNano()) 87 | rnd := rand.New(src) 88 | 89 | bytes := make([]byte, 16) 90 | 91 | rnd.Read(bytes) 92 | 93 | return net.IP{bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 94 | bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]} 95 | } 96 | 97 | // GetRandomIP is return a random IP address. 98 | // The returned IP *can be* a reserved address. 99 | // The version of the IP protocol is random. 100 | func GetRandomIP() net.IP { 101 | 102 | n := rand.Intn(2) 103 | 104 | if n == 0 { 105 | return GetRandomIPv4() 106 | } else { 107 | return GetRandomIPv6() 108 | } 109 | } 110 | 111 | // GetPublicIPv4 is return a **non reserved** IPv4 address. 112 | func GetPublicIPv4() net.IP { 113 | 114 | for { 115 | ip := GetRandomIPv4() 116 | 117 | if !IsReservedIPv4(ip) { 118 | return ip 119 | } 120 | } 121 | } 122 | 123 | // GetPublicIPv6 is return a **non reserved** IPv6 address. 124 | func GetPublicIPv6() net.IP { 125 | 126 | for { 127 | ip := GetRandomIPv6() 128 | 129 | if !IsReservedIPv6(ip) { 130 | return ip 131 | } 132 | } 133 | } 134 | 135 | // GetPublicIP is return a **non reserved** IP address. 136 | // The version of the IP protocol is random. 137 | func GetPublicIP() net.IP { 138 | 139 | n := rand.Intn(2) 140 | 141 | if n == 0 { 142 | return GetPublicIPv4() 143 | } else { 144 | return GetPublicIPv6() 145 | } 146 | } 147 | --------------------------------------------------------------------------------