Initial commit
This commit is contained in:
commit
ccc5b0cd98
26 changed files with 1806 additions and 0 deletions
142
checker/starttls_ldap.go
Normal file
142
checker/starttls_ldap.go
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerStartTLS("ldap", starttlsLDAP)
|
||||
}
|
||||
|
||||
// starttlsLDAP implements RFC 2830 StartTLS.
|
||||
//
|
||||
// It sends a single ExtendedRequest with OID 1.3.6.1.4.1.1466.20037 on
|
||||
// messageID=1 and reads back the ExtendedResponse. On resultCode 0 the
|
||||
// connection is ready for the TLS handshake. On resultCode 2
|
||||
// (protocolError) or 53 (unwillingToPerform) we wrap errStartTLSNotOffered
|
||||
// -- the server is reachable but cannot upgrade -- so the caller can surface
|
||||
// that as a missing-STARTTLS issue rather than a handshake failure.
|
||||
func starttlsLDAP(conn net.Conn, sni string) error {
|
||||
// Fixed LDAPMessage:
|
||||
// SEQUENCE {
|
||||
// INTEGER messageID = 1,
|
||||
// [APPLICATION 23] SEQUENCE {
|
||||
// [0] OCTET STRING "1.3.6.1.4.1.1466.20037"
|
||||
// }
|
||||
// }
|
||||
request := []byte{
|
||||
0x30, 0x1d,
|
||||
0x02, 0x01, 0x01,
|
||||
0x77, 0x18,
|
||||
0x80, 0x16,
|
||||
'1', '.', '3', '.', '6', '.', '1', '.', '4', '.', '1', '.',
|
||||
'1', '4', '6', '6', '.', '2', '0', '0', '3', '7',
|
||||
}
|
||||
if _, err := conn.Write(request); err != nil {
|
||||
return fmt.Errorf("write StartTLS request: %w", err)
|
||||
}
|
||||
|
||||
r := bufio.NewReader(conn)
|
||||
|
||||
tag, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
if tag != 0x30 {
|
||||
return fmt.Errorf("unexpected LDAP response tag 0x%02x", tag)
|
||||
}
|
||||
length, err := readBERLength(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read response length: %w", err)
|
||||
}
|
||||
if length <= 0 || length > 4096 {
|
||||
return fmt.Errorf("unreasonable LDAP response length %d", length)
|
||||
}
|
||||
body := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, body); err != nil {
|
||||
return fmt.Errorf("read response body: %w", err)
|
||||
}
|
||||
|
||||
// messageID INTEGER -- skip.
|
||||
pos := 0
|
||||
if pos >= len(body) || body[pos] != 0x02 {
|
||||
return fmt.Errorf("expected INTEGER messageID")
|
||||
}
|
||||
pos++
|
||||
if pos >= len(body) {
|
||||
return fmt.Errorf("truncated messageID length")
|
||||
}
|
||||
msgIDLen := int(body[pos])
|
||||
pos++
|
||||
pos += msgIDLen
|
||||
if pos >= len(body) {
|
||||
return fmt.Errorf("truncated LDAP response")
|
||||
}
|
||||
|
||||
// [APPLICATION 24] constructed = 0x78.
|
||||
if body[pos] != 0x78 {
|
||||
return fmt.Errorf("expected ExtendedResponse tag, got 0x%02x", body[pos])
|
||||
}
|
||||
pos++
|
||||
// Skip extendedResp length (possibly multi-byte).
|
||||
if pos >= len(body) {
|
||||
return fmt.Errorf("truncated ExtendedResponse length")
|
||||
}
|
||||
if body[pos] < 0x80 {
|
||||
pos++
|
||||
} else {
|
||||
n := int(body[pos] & 0x7f)
|
||||
pos += 1 + n
|
||||
}
|
||||
|
||||
// resultCode ENUMERATED (tag 0x0a).
|
||||
if pos+2 > len(body) || body[pos] != 0x0a {
|
||||
return fmt.Errorf("expected resultCode ENUMERATED")
|
||||
}
|
||||
rcLen := int(body[pos+1])
|
||||
if rcLen < 1 || pos+2+rcLen > len(body) {
|
||||
return fmt.Errorf("invalid resultCode length %d", rcLen)
|
||||
}
|
||||
rc := 0
|
||||
for i := 0; i < rcLen; i++ {
|
||||
rc = (rc << 8) | int(body[pos+2+i])
|
||||
}
|
||||
switch rc {
|
||||
case 0:
|
||||
return nil
|
||||
case 2, 53:
|
||||
return fmt.Errorf("%w: LDAP StartTLS refused (resultCode=%d)", errStartTLSNotOffered, rc)
|
||||
default:
|
||||
return fmt.Errorf("server refused StartTLS (LDAP resultCode=%d)", rc)
|
||||
}
|
||||
}
|
||||
|
||||
// readBERLength reads a definite-form BER length (short or long form).
|
||||
func readBERLength(r *bufio.Reader) (int, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if b < 0x80 {
|
||||
return int(b), nil
|
||||
}
|
||||
n := int(b & 0x7f)
|
||||
if n == 0 {
|
||||
return 0, fmt.Errorf("indefinite-form length not supported")
|
||||
}
|
||||
if n > 4 {
|
||||
return 0, fmt.Errorf("length octet count %d too large", n)
|
||||
}
|
||||
length := 0
|
||||
for i := 0; i < n; i++ {
|
||||
bb, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
length = (length << 8) | int(bb)
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue