Skip to content

Commit

Permalink
Merge pull request #664 from gopcua/secure-channel-do-not-change-config
Browse files Browse the repository at this point in the history
uasc: return an error for invalid uri/mode combinations with None
  • Loading branch information
kung-foo committed Jun 15, 2023
2 parents 1130b79 + d5aaaaf commit 807c2da
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 13 deletions.
20 changes: 9 additions & 11 deletions uasc/secure_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,17 @@ func NewSecureChannel(endpoint string, c *uacp.Conn, cfg *Config, errCh chan<- e
return nil, errors.Errorf("no secure channel config")
}

if cfg.SecurityPolicyURI != ua.SecurityPolicyURINone {
if cfg.SecurityMode == ua.MessageSecurityModeNone {
return nil, errors.Errorf("invalid channel config: Security policy '%s' cannot be used with '%s'", cfg.SecurityPolicyURI, cfg.SecurityMode)
}
if cfg.LocalKey == nil {
return nil, errors.Errorf("invalid channel config: Security policy '%s' requires a private key", cfg.SecurityPolicyURI)
}
if errCh == nil {
return nil, errors.Errorf("no error channel")
}

// Force the security mode to None if the policy is also None
// TODO: I don't like that a SecureChannel changes the incoming config
if cfg.SecurityPolicyURI == ua.SecurityPolicyURINone {
cfg.SecurityMode = ua.MessageSecurityModeNone
switch {
case cfg.SecurityPolicyURI == ua.SecurityPolicyURINone && cfg.SecurityMode != ua.MessageSecurityModeNone:
return nil, errors.Errorf("invalid channel config: Security policy '%s' cannot be used with '%s'", cfg.SecurityPolicyURI, cfg.SecurityMode)
case cfg.SecurityPolicyURI != ua.SecurityPolicyURINone && cfg.SecurityMode == ua.MessageSecurityModeNone:
return nil, errors.Errorf("invalid channel config: Security policy '%s' cannot be used with '%s'", cfg.SecurityPolicyURI, cfg.SecurityMode)
case cfg.SecurityPolicyURI != ua.SecurityPolicyURINone && cfg.LocalKey == nil:
return nil, errors.Errorf("invalid channel config: Security policy '%s' requires a private key", cfg.SecurityPolicyURI)
}

s := &SecureChannel{
Expand Down
51 changes: 49 additions & 2 deletions uasc/secure_channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (
"encoding/pem"
"fmt"
"math"
"strings"
"testing"
"time"

"github.com/gopcua/opcua/id"
"github.com/gopcua/opcua/ua"
"github.com/gopcua/opcua/uacp"
"github.com/gopcua/opcua/uapolicy"
"github.com/gopcua/opcua/uatest"

"github.com/pascaldekloe/goe/verify"
)

Expand Down Expand Up @@ -146,7 +147,7 @@ func TestNewRequestMessage(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
m, err := tt.sechan.activeInstance.newRequestMessage(tt.req, tt.sechan.nextRequestID(), tt.authToken, tt.timeout)
if err != nil {
t.Fatalf("got err %v want nil", err)
t.Fatal(err)
}
verify.Values(t, "", m, tt.m)
})
Expand All @@ -158,16 +159,22 @@ func TestSignAndEncryptVerifyAndDecrypt(t *testing.T) {
t.Helper()

certPEM, keyPEM, err := uatest.GenerateCert("localhost", bits, 24*time.Hour)
if err != nil {
t.Fatal(err)
}

block, _ := pem.Decode(keyPEM)
pk, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
t.Fatal(err)
}

certblock, _ := pem.Decode(certPEM)
remoteX509Cert, err := x509.ParseCertificate(certblock.Bytes)
if err != nil {
t.Fatal(err)
}

remoteKey := remoteX509Cert.PublicKey.(*rsa.PublicKey)
alg, _ := uapolicy.Asymmetric(uri, pk, remoteKey)
return alg
Expand Down Expand Up @@ -306,3 +313,43 @@ func TestSignAndEncryptVerifyAndDecrypt(t *testing.T) {
})
}
}

func TestNewSecureChannel(t *testing.T) {
t.Run("no connection", func(t *testing.T) {
_, err := NewSecureChannel("", nil, nil, nil)
errorContains(t, err, "no connection")
})
t.Run("no error channel", func(t *testing.T) {
_, err := NewSecureChannel("", &uacp.Conn{}, nil, nil)
errorContains(t, err, "no secure channel config")
})
t.Run("no config", func(t *testing.T) {
_, err := NewSecureChannel("", &uacp.Conn{}, nil, make(chan error))
errorContains(t, err, "no secure channel config")
})
t.Run("uri none, mode not none", func(t *testing.T) {
cfg := &Config{SecurityPolicyURI: ua.SecurityPolicyURINone, SecurityMode: ua.MessageSecurityModeSign}
_, err := NewSecureChannel("", &uacp.Conn{}, cfg, make(chan error))
errorContains(t, err, "invalid channel config: Security policy 'http://opcfoundation.org/UA/SecurityPolicy#None' cannot be used with 'MessageSecurityModeSign'")
})
t.Run("uri not none, mode none", func(t *testing.T) {
cfg := &Config{SecurityPolicyURI: ua.SecurityPolicyURIBasic256, SecurityMode: ua.MessageSecurityModeNone}
_, err := NewSecureChannel("", &uacp.Conn{}, cfg, make(chan error))
errorContains(t, err, "Security policy 'http://opcfoundation.org/UA/SecurityPolicy#Basic256' cannot be used with 'MessageSecurityModeNone'")
})
t.Run("uri not none, local key missing", func(t *testing.T) {
cfg := &Config{SecurityPolicyURI: ua.SecurityPolicyURIBasic256, SecurityMode: ua.MessageSecurityModeSign}
_, err := NewSecureChannel("", &uacp.Conn{}, cfg, make(chan error))
errorContains(t, err, "invalid channel config: Security policy 'http://opcfoundation.org/UA/SecurityPolicy#Basic256' requires a private key")
})
}

func errorContains(t *testing.T, err error, msg string) {
t.Helper()
if err == nil {
t.Fatal("expected an error but got nil")
}
if !strings.Contains(err.Error(), msg) {
t.Fatalf("error '%s' does not contain '%s'", err, msg)
}
}

0 comments on commit 807c2da

Please sign in to comment.