Merge pull request #35 from 0x524a/tls-verification

Tls verification
This commit is contained in:
ProtoTess
2025-11-18 14:27:37 -05:00
committed by GitHub
3 changed files with 99 additions and 29 deletions
+10 -1
View File
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.1.3] - 2025-11-18
### Changed
- **Release Workflow**: Create releases as draft initially
- Fixes "Cannot upload assets to an immutable release" error
- Releases must be manually published after assets upload
- Prevents race condition where release publishes before all assets finish uploading
## [1.1.2] - 2025-11-18
### Changed
@@ -107,7 +115,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Comprehensive documentation
- README with usage guide
[Unreleased]: https://github.com/0x524a/onvif-go/compare/v1.1.2...HEAD
[Unreleased]: https://github.com/0x524a/onvif-go/compare/v1.1.3...HEAD
[1.1.3]: https://github.com/0x524a/onvif-go/compare/v1.1.2...v1.1.3
[1.1.2]: https://github.com/0x524a/onvif-go/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/0x524a/onvif-go/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/0x524a/onvif-go/compare/v1.0.3...v1.1.0
+19
View File
@@ -3,6 +3,7 @@ package onvif
import (
"context"
"crypto/md5"
"crypto/tls"
"fmt"
"io"
"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
func WithCredentials(username, password string) ClientOption {
return func(c *Client) {
@@ -74,6 +88,11 @@ func NewClient(endpoint string, opts ...ClientOption) (*Client, error) {
MaxIdleConnsPerHost: 5,
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
},
},
}
+70 -28
View File
@@ -282,13 +282,26 @@ func (c *CLI) connectToDiscoveredCamera(device *discovery.Device) {
endpoint := device.GetDeviceEndpoint()
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")
fmt.Print("Password: ")
password, _ := c.reader.ReadString('\n')
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() {
@@ -296,23 +309,42 @@ func (c *CLI) connectToCamera() {
fmt.Println("===================")
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")
fmt.Print("Password: ")
password, _ := c.reader.ReadString('\n')
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...")
client, err := onvif.NewClient(
endpoint,
opts := []onvif.ClientOption{
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 {
fmt.Printf("❌ Failed to create client: %v\n", err)
return
@@ -328,6 +360,9 @@ func (c *CLI) createClient(endpoint, username, password string) {
fmt.Println(" - Endpoint URL is correct")
fmt.Println(" - Username and password are correct")
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
}
@@ -532,7 +567,6 @@ func (c *CLI) inspectRTSPStream(streamURI string) map[string]interface{} {
"reachable": false,
"codec": "unknown",
"resolution": "unknown",
"framerate": "unknown",
}
// Use rtspeek library for detailed stream inspection
@@ -543,25 +577,22 @@ func (c *CLI) inspectRTSPStream(streamURI string) map[string]interface{} {
if err == nil && streamInfo != nil {
details["reachable"] = streamInfo.IsReachable()
if streamInfo.IsDescribeSucceeded() {
if streamInfo.IsDescribeSucceeded() && streamInfo.HasVideo() {
// Extract codec information from first video media
if firstVideo := streamInfo.GetFirstVideoMedia(); firstVideo != nil {
// Get codec format (H264, H265, MJPEG, etc.)
details["codec"] = firstVideo.Format
}
// Extract resolution
resolutions := streamInfo.GetVideoResolutionStrings()
if len(resolutions) > 0 {
details["resolution"] = resolutions[0]
}
// Try to extract framerate (typical RTSP codecs run at standard framerates)
if firstVideo := streamInfo.GetFirstVideoMedia(); firstVideo != nil {
if firstVideo.ClockRate != nil && *firstVideo.ClockRate > 0 {
// H.264/H.265 typically use 90kHz clock with 1 frame per 3000-3600 samples
// This is a heuristic; actual framerate may vary
if firstVideo.Format == "H264" || firstVideo.Format == "H265" {
details["framerate"] = "30 fps"
// Extract resolution directly from the video media
if firstVideo.Resolution != nil {
details["resolution"] = fmt.Sprintf("%dx%d",
firstVideo.Resolution.Width,
firstVideo.Resolution.Height)
} else {
// Fallback to resolution strings
resolutions := streamInfo.GetVideoResolutionStrings()
if len(resolutions) > 0 {
details["resolution"] = resolutions[0]
}
}
}
@@ -642,6 +673,13 @@ func (c *CLI) getStreamURIs(ctx context.Context) {
fmt.Printf(" Stream URI: ❌ Error - %v\n", err)
} else {
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
fmt.Print(" ⏳ Inspecting stream details...")
@@ -664,10 +702,6 @@ func (c *CLI) getStreamURIs(ctx context.Context) {
fmt.Printf(" Resolution: %s\n", resolution)
}
if framerate, ok := details["framerate"].(string); ok && framerate != "unknown" {
fmt.Printf(" Frame Rate: %s\n", framerate)
}
if port, ok := details["port"].(string); ok {
fmt.Printf(" RTSP Port: %s\n", port)
}
@@ -701,6 +735,14 @@ func (c *CLI) getSnapshotURIs(ctx context.Context) {
fmt.Printf(" Snapshot URI: ❌ Error - %v\n", err)
} else {
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.Println()