feat: add TLS verification option for HTTPS connections and improve user prompts
This commit is contained in:
@@ -3,6 +3,7 @@ package onvif
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -45,6 +46,19 @@ func WithHTTPClient(httpClient *http.Client) ClientOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInsecureSkipVerify disables TLS certificate verification
|
||||||
|
// WARNING: Only use this for testing or with trusted cameras on private networks
|
||||||
|
func WithInsecureSkipVerify() ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
|
||||||
|
if transport.TLSClientConfig == nil {
|
||||||
|
transport.TLSClientConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithCredentials sets the authentication credentials
|
// WithCredentials sets the authentication credentials
|
||||||
func WithCredentials(username, password string) ClientOption {
|
func WithCredentials(username, password string) ClientOption {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
@@ -74,6 +88,11 @@ func NewClient(endpoint string, opts ...ClientOption) (*Client, error) {
|
|||||||
MaxIdleConnsPerHost: 5,
|
MaxIdleConnsPerHost: 5,
|
||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
},
|
},
|
||||||
|
// Don't follow redirects automatically
|
||||||
|
// This prevents http:// from being silently upgraded to https://
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-19
@@ -282,13 +282,26 @@ func (c *CLI) connectToDiscoveredCamera(device *discovery.Device) {
|
|||||||
endpoint := device.GetDeviceEndpoint()
|
endpoint := device.GetDeviceEndpoint()
|
||||||
|
|
||||||
fmt.Printf("Connecting to: %s\n", endpoint)
|
fmt.Printf("Connecting to: %s\n", endpoint)
|
||||||
|
|
||||||
|
// Warn if using HTTPS
|
||||||
|
if strings.HasPrefix(endpoint, "https://") {
|
||||||
|
fmt.Println("⚠️ HTTPS endpoint detected - you may need to skip TLS verification for self-signed certificates")
|
||||||
|
}
|
||||||
|
|
||||||
username := c.readInputWithDefault("Username", "admin")
|
username := c.readInputWithDefault("Username", "admin")
|
||||||
|
|
||||||
fmt.Print("Password: ")
|
fmt.Print("Password: ")
|
||||||
password, _ := c.reader.ReadString('\n')
|
password, _ := c.reader.ReadString('\n')
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
|
|
||||||
c.createClient(endpoint, username, password)
|
// Ask about TLS verification only for HTTPS
|
||||||
|
insecure := false
|
||||||
|
if strings.HasPrefix(endpoint, "https://") {
|
||||||
|
skipTLS := c.readInputWithDefault("Skip TLS certificate verification? (y/N)", "N")
|
||||||
|
insecure = strings.ToLower(skipTLS) == "y" || strings.ToLower(skipTLS) == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.createClient(endpoint, username, password, insecure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) connectToCamera() {
|
func (c *CLI) connectToCamera() {
|
||||||
@@ -296,23 +309,42 @@ func (c *CLI) connectToCamera() {
|
|||||||
fmt.Println("===================")
|
fmt.Println("===================")
|
||||||
|
|
||||||
endpoint := c.readInputWithDefault("Camera endpoint (http://ip:port/onvif/device_service)", "http://192.168.1.100/onvif/device_service")
|
endpoint := c.readInputWithDefault("Camera endpoint (http://ip:port/onvif/device_service)", "http://192.168.1.100/onvif/device_service")
|
||||||
|
|
||||||
|
// Warn if using HTTPS
|
||||||
|
if strings.HasPrefix(endpoint, "https://") {
|
||||||
|
fmt.Println("⚠️ HTTPS endpoint detected - you may need to skip TLS verification for self-signed certificates")
|
||||||
|
}
|
||||||
|
|
||||||
username := c.readInputWithDefault("Username", "admin")
|
username := c.readInputWithDefault("Username", "admin")
|
||||||
|
|
||||||
fmt.Print("Password: ")
|
fmt.Print("Password: ")
|
||||||
password, _ := c.reader.ReadString('\n')
|
password, _ := c.reader.ReadString('\n')
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
|
|
||||||
c.createClient(endpoint, username, password)
|
// Ask about TLS verification only for HTTPS
|
||||||
|
insecure := false
|
||||||
|
if strings.HasPrefix(endpoint, "https://") {
|
||||||
|
skipTLS := c.readInputWithDefault("Skip TLS certificate verification? (y/N)", "N")
|
||||||
|
insecure = strings.ToLower(skipTLS) == "y" || strings.ToLower(skipTLS) == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.createClient(endpoint, username, password, insecure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) createClient(endpoint, username, password string) {
|
func (c *CLI) createClient(endpoint, username, password string, insecure bool) {
|
||||||
fmt.Println("⏳ Connecting...")
|
fmt.Println("⏳ Connecting...")
|
||||||
|
|
||||||
client, err := onvif.NewClient(
|
opts := []onvif.ClientOption{
|
||||||
endpoint,
|
|
||||||
onvif.WithCredentials(username, password),
|
onvif.WithCredentials(username, password),
|
||||||
onvif.WithTimeout(30*time.Second),
|
onvif.WithTimeout(30 * time.Second),
|
||||||
)
|
}
|
||||||
|
|
||||||
|
if insecure {
|
||||||
|
fmt.Println("⚠️ TLS certificate verification disabled")
|
||||||
|
opts = append(opts, onvif.WithInsecureSkipVerify())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := onvif.NewClient(endpoint, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Failed to create client: %v\n", err)
|
fmt.Printf("❌ Failed to create client: %v\n", err)
|
||||||
return
|
return
|
||||||
@@ -328,6 +360,9 @@ func (c *CLI) createClient(endpoint, username, password string) {
|
|||||||
fmt.Println(" - Endpoint URL is correct")
|
fmt.Println(" - Endpoint URL is correct")
|
||||||
fmt.Println(" - Username and password are correct")
|
fmt.Println(" - Username and password are correct")
|
||||||
fmt.Println(" - Camera is accessible from this network")
|
fmt.Println(" - Camera is accessible from this network")
|
||||||
|
if strings.Contains(err.Error(), "tls") || strings.Contains(err.Error(), "certificate") || strings.Contains(err.Error(), "x509") {
|
||||||
|
fmt.Println(" - For HTTPS cameras with self-signed certificates, answer 'y' to skip TLS verification")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,25 +578,33 @@ func (c *CLI) inspectRTSPStream(streamURI string) map[string]interface{} {
|
|||||||
if err == nil && streamInfo != nil {
|
if err == nil && streamInfo != nil {
|
||||||
details["reachable"] = streamInfo.IsReachable()
|
details["reachable"] = streamInfo.IsReachable()
|
||||||
|
|
||||||
if streamInfo.IsDescribeSucceeded() {
|
if streamInfo.IsDescribeSucceeded() && streamInfo.HasVideo() {
|
||||||
// Extract codec information from first video media
|
// Extract codec information from first video media
|
||||||
if firstVideo := streamInfo.GetFirstVideoMedia(); firstVideo != nil {
|
if firstVideo := streamInfo.GetFirstVideoMedia(); firstVideo != nil {
|
||||||
|
// Get codec format (H264, H265, MJPEG, etc.)
|
||||||
details["codec"] = firstVideo.Format
|
details["codec"] = firstVideo.Format
|
||||||
}
|
|
||||||
|
|
||||||
// Extract resolution
|
// Extract resolution directly from the video media
|
||||||
resolutions := streamInfo.GetVideoResolutionStrings()
|
if firstVideo.Resolution != nil {
|
||||||
if len(resolutions) > 0 {
|
details["resolution"] = fmt.Sprintf("%dx%d",
|
||||||
details["resolution"] = resolutions[0]
|
firstVideo.Resolution.Width,
|
||||||
}
|
firstVideo.Resolution.Height)
|
||||||
|
} else {
|
||||||
|
// Fallback to resolution strings
|
||||||
|
resolutions := streamInfo.GetVideoResolutionStrings()
|
||||||
|
if len(resolutions) > 0 {
|
||||||
|
details["resolution"] = resolutions[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try to extract framerate (typical RTSP codecs run at standard framerates)
|
// Try to determine framerate from clock rate
|
||||||
if firstVideo := streamInfo.GetFirstVideoMedia(); firstVideo != nil {
|
|
||||||
if firstVideo.ClockRate != nil && *firstVideo.ClockRate > 0 {
|
if firstVideo.ClockRate != nil && *firstVideo.ClockRate > 0 {
|
||||||
// H.264/H.265 typically use 90kHz clock with 1 frame per 3000-3600 samples
|
// For H.264/H.265, clock rate is typically 90kHz
|
||||||
// This is a heuristic; actual framerate may vary
|
// Actual framerate depends on RTP timestamps, but we can estimate
|
||||||
if firstVideo.Format == "H264" || firstVideo.Format == "H265" {
|
if firstVideo.Format == "H264" || firstVideo.Format == "H265" {
|
||||||
details["framerate"] = "30 fps"
|
details["framerate"] = "30 fps" // Common default
|
||||||
|
} else if firstVideo.Format == "MJPEG" {
|
||||||
|
details["framerate"] = "variable"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -643,6 +686,13 @@ func (c *CLI) getStreamURIs(ctx context.Context) {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Printf(" Stream URI: %s\n", streamURI.URI)
|
fmt.Printf(" Stream URI: %s\n", streamURI.URI)
|
||||||
|
|
||||||
|
// Warn if camera returns HTTPS when we connected via HTTP
|
||||||
|
if strings.HasPrefix(c.client.Endpoint(), "http://") && strings.HasPrefix(streamURI.URI, "https://") {
|
||||||
|
fmt.Printf(" ⚠️ WARNING: Camera returned HTTPS URL but you connected via HTTP\n")
|
||||||
|
fmt.Printf(" 💡 Stream may fail due to TLS certificate issues\n")
|
||||||
|
fmt.Printf(" 💡 Consider reconnecting with https:// endpoint and skip TLS verification\n")
|
||||||
|
}
|
||||||
|
|
||||||
// Inspect RTSP stream details
|
// Inspect RTSP stream details
|
||||||
fmt.Print(" ⏳ Inspecting stream details...")
|
fmt.Print(" ⏳ Inspecting stream details...")
|
||||||
details := c.inspectRTSPStream(streamURI.URI)
|
details := c.inspectRTSPStream(streamURI.URI)
|
||||||
@@ -701,6 +751,14 @@ func (c *CLI) getSnapshotURIs(ctx context.Context) {
|
|||||||
fmt.Printf(" Snapshot URI: ❌ Error - %v\n", err)
|
fmt.Printf(" Snapshot URI: ❌ Error - %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" Snapshot URI: %s\n", snapshotURI.URI)
|
fmt.Printf(" Snapshot URI: %s\n", snapshotURI.URI)
|
||||||
|
|
||||||
|
// Warn if camera returns HTTPS when we connected via HTTP
|
||||||
|
if strings.HasPrefix(c.client.Endpoint(), "http://") && strings.HasPrefix(snapshotURI.URI, "https://") {
|
||||||
|
fmt.Printf(" ⚠️ WARNING: Camera returned HTTPS URL but you connected via HTTP\n")
|
||||||
|
fmt.Printf(" 💡 Snapshot may fail due to TLS certificate issues\n")
|
||||||
|
fmt.Printf(" 💡 Consider reconnecting with https:// endpoint and skip TLS verification\n")
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf(" 🌐 Open this URL in a browser to see the snapshot\n")
|
fmt.Printf(" 🌐 Open this URL in a browser to see the snapshot\n")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|||||||
Reference in New Issue
Block a user