Repository: gopcua/opcua
Pull request #182 (2019-05-06 20:40:56)

Fix Invalid PolicyID on Anonymous connections
After sending a `CreateSessionRequest`, check if the UserIdentityToken was left unset by the user and set it to anonymous auth with a valid policy ID from the server.


Patch:
diff --git a/client.go b/client.go
index 3fad684..ed5232c 100644
--- a/client.go
+++ b/client.go
@@ -64,13 +64,6 @@ func NewClient(endpoint string, opts ...Option) *Client {
 		opt(c.cfg, c.sessionCfg)
 	}
 
-	// UserIdentityToken was removed from DefaultSessionConfig() so ensure a default still is set
-	if c.sessionCfg.UserIdentityToken == nil {
-		opt := AuthAnonymous()
-		opt(c.cfg, c.sessionCfg)
-		opt = AuthPolicyID("Anonymous")
-		opt(c.cfg, c.sessionCfg)
-	}
 	return c
 }
 
@@ -187,6 +180,16 @@ func (c *Client) CreateSession(cfg *uasc.SessionConfig) (*Session, error) {
 			return nil
 		}
 
+		// Ensure we have a valid identity token that the server will accept before trying to activate a session
+		if c.sessionCfg.UserIdentityToken == nil {
+			opt := AuthAnonymous()
+			opt(c.cfg, c.sessionCfg)
+
+			p := anonymousPolicyID(resp.ServerEndpoints)
+			opt = AuthPolicyID(p)
+			opt(c.cfg, c.sessionCfg)
+		}
+
 		s = &Session{
 			cfg:               cfg,
 			resp:              resp,
@@ -199,6 +202,24 @@ func (c *Client) CreateSession(cfg *uasc.SessionConfig) (*Session, error) {
 	return s, err
 }
 
+const defaultAnonymousPolicyID = "Anonymous"
+
+func anonymousPolicyID(endpoints []*ua.EndpointDescription) string {
+	for _, e := range endpoints {
+		if e.SecurityMode != ua.MessageSecurityModeNone || e.SecurityPolicyURI != ua.SecurityPolicyURINone {
+			continue
+		}
+
+		for _, t := range e.UserIdentityTokens {
+			if t.TokenType == ua.UserTokenTypeAnonymous {
+				return t.PolicyID
+			}
+		}
+	}
+
+	return defaultAnonymousPolicyID
+}
+
 // ActivateSession activates the session and associates it with the client. If
 // the client already has a session it will be closed. To retain the current
 // session call DetachSession.
diff --git a/config.go b/config.go
index 7f107c9..722f085 100644
--- a/config.go
+++ b/config.go
@@ -21,7 +21,7 @@ import (
 // to establish a secure channel and a session.
 func DefaultClientConfig() *uasc.Config {
 	return &uasc.Config{
-		SecurityPolicyURI: uapolicy.SecurityPolicyNone,
+		SecurityPolicyURI: ua.SecurityPolicyURINone,
 		SecurityMode:      ua.MessageSecurityModeNone,
 		Lifetime:          uint32(time.Hour / time.Millisecond),
 	}
@@ -165,8 +165,8 @@ func SecurityFromEndpoint(ep *ua.EndpointDescription, authType ua.UserTokenType)
 		}
 
 		if sc.UserIdentityToken == nil {
-			sc.UserIdentityToken = &ua.AnonymousIdentityToken{PolicyID: "Anonymous"}
-			sc.AuthPolicyURI = uapolicy.SecurityPolicyNone
+			sc.UserIdentityToken = &ua.AnonymousIdentityToken{PolicyID: defaultAnonymousPolicyID}
+			sc.AuthPolicyURI = ua.SecurityPolicyURINone
 		}
 
 	}
diff --git a/examples/crypto/crypto.go b/examples/crypto/crypto.go
index cf90777..86abf6e 100644
--- a/examples/crypto/crypto.go
+++ b/examples/crypto/crypto.go
@@ -18,7 +18,6 @@ import (
 	"github.com/gopcua/opcua"
 	"github.com/gopcua/opcua/debug"
 	"github.com/gopcua/opcua/ua"
-	"github.com/gopcua/opcua/uapolicy"
 
 	"golang.org/x/crypto/ssh/terminal"
 )
@@ -136,11 +135,11 @@ func clientOptsFromFlags(endpoints []*ua.EndpointDescription) []opcua.Option {
 	switch {
 	case *policy == "auto":
 		// set it later
-	case strings.HasPrefix(*policy, uapolicy.SecurityPolicyURL):
+	case strings.HasPrefix(*policy, ua.SecurityPolicyURIPrefix):
 		secPolicy = *policy
 		*policy = ""
 	case *policy == "Basic128Rsa15" || *policy == "Basic256" || *policy == "Basic256Sha256":
-		secPolicy = uapolicy.SecurityPolicyURL + *policy
+		secPolicy = ua.SecurityPolicyURIPrefix + *policy
 		*policy = ""
 	default:
 		log.Fatalf("Invalid security policy: %s", *policy)
@@ -167,9 +166,9 @@ func clientOptsFromFlags(endpoints []*ua.EndpointDescription) []opcua.Option {
 	}
 
 	// Allow input of only one of sec-mode,sec-policy when choosing 'None'
-	if secMode == ua.MessageSecurityModeNone || secPolicy == uapolicy.SecurityPolicyNone {
+	if secMode == ua.MessageSecurityModeNone || secPolicy == ua.SecurityPolicyURINone {
 		secMode = ua.MessageSecurityModeNone
-		secPolicy = uapolicy.SecurityPolicyNone
+		secPolicy = ua.SecurityPolicyURINone
 	}
 
 	// Find the best endpoint based on our input and server recommendation (highest SecurityMode+SecurityLevel)
diff --git a/ua/enums.go b/ua/enums.go
index c406c8e..ba9f0f5 100644
--- a/ua/enums.go
+++ b/ua/enums.go
@@ -82,3 +82,16 @@ const (
 	TypeIDVariant         TypeID = 24
 	TypeIDDiagnosticInfo  TypeID = 25
 )
+
+// SecurityPolicyURI is a listing of UA security policy URIs
+// Specification: Part 7, 6.6.161-166
+
+const (
+	SecurityPolicyURIPrefix              = "http://opcfoundation.org/UA/SecurityPolicy#"
+	SecurityPolicyURINone                = "http://opcfoundation.org/UA/SecurityPolicy#None"
+	SecurityPolicyURIBasic128Rsa15       = "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"
+	SecurityPolicyURIBasic256            = "http://opcfoundation.org/UA/SecurityPolicy#Basic256"
+	SecurityPolicyURIBasic256Sha256      = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
+	SecurityPolicyURIAes128Sha256RsaOaep = "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"
+	SecurityPolicyURIAes256Sha256RsaPss  = "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss"
+)
diff --git a/uapolicy/securitypolicy.go b/uapolicy/securitypolicy.go
index edae8aa..52d32ee 100644
--- a/uapolicy/securitypolicy.go
+++ b/uapolicy/securitypolicy.go
@@ -12,16 +12,8 @@ import (
 	"errors"
 	"fmt"
 	"sort"
-)
 
-const (
-	SecurityPolicyURL                 = "http://opcfoundation.org/UA/SecurityPolicy#"
-	SecurityPolicyNone                = "http://opcfoundation.org/UA/SecurityPolicy#None"
-	SecurityPolicyBasic128Rsa15       = "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"
-	SecurityPolicyBasic256            = "http://opcfoundation.org/UA/SecurityPolicy#Basic256"
-	SecurityPolicyBasic256Sha256      = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
-	SecurityPolicyAes128Sha256RsaOaep = "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"
-	SecurityPolicyAes256Sha256RsaPss  = "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss"
+	"github.com/gopcua/opcua/ua"
 )
 
 // SupportedPolicies returns all supported Security Policies
@@ -42,21 +34,21 @@ func Asymmetric(uri string, localKey *rsa.PrivateKey, remoteKey *rsa.PublicKey)
 		return nil, fmt.Errorf("unsupported security policy %s", uri)
 	}
 
-	if uri != SecurityPolicyNone && (localKey == nil || remoteKey == nil) {
+	if uri != ua.SecurityPolicyURINone && (localKey == nil || remoteKey == nil) {
 		return nil, errors.New("invalid asymmetric security policy config: both keys required")
 	}
 
 	return p.asymmetric(localKey, remoteKey)
 }
 
-// Symmetrics returns the symmetric encryption algorithm for the given security policy.
+// Symmetric returns the symmetric encryption algorithm for the given security policy.
 func Symmetric(uri string, localNonce, remoteNonce []byte) (*EncryptionAlgorithm, error) {
 	p, ok := policies[uri]
 	if !ok {
 		return nil, fmt.Errorf("unsupported security policy %s", uri)
 	}
 
-	if uri != SecurityPolicyNone && (localNonce == nil || remoteNonce == nil) {
+	if uri != ua.SecurityPolicyURINone && (localNonce == nil || remoteNonce == nil) {
 		return nil, errors.New("invalid symmetric security policy config: both nonces required")
 	}
 
@@ -165,12 +157,12 @@ func (e *EncryptionAlgorithm) SignatureURI() string {
 }
 
 var policies = map[string]policy{
-	SecurityPolicyNone:                {newNoneAsymmetric, newNoneSymmetric},
-	SecurityPolicyBasic128Rsa15:       {newBasic128Rsa15Asymmetric, newBasic128Rsa15Symmetric},
-	SecurityPolicyBasic256:            {newBasic256Asymmetric, newBasic256Symmetric},
-	SecurityPolicyBasic256Sha256:      {newBasic256Rsa256Asymmetric, newBasic256Rsa256Symmetric},
-	SecurityPolicyAes128Sha256RsaOaep: {newAes128Sha256RsaOaepAsymmetric, newAes128Sha256RsaOaepSymmetric},
-	SecurityPolicyAes256Sha256RsaPss:  {newAes256Sha256RsaPssAsymmetric, newAes256Sha256RsaPssSymmetric},
+	ua.SecurityPolicyURINone:                {newNoneAsymmetric, newNoneSymmetric},
+	ua.SecurityPolicyURIBasic128Rsa15:       {newBasic128Rsa15Asymmetric, newBasic128Rsa15Symmetric},
+	ua.SecurityPolicyURIBasic256:            {newBasic256Asymmetric, newBasic256Symmetric},
+	ua.SecurityPolicyURIBasic256Sha256:      {newBasic256Rsa256Asymmetric, newBasic256Rsa256Symmetric},
+	ua.SecurityPolicyURIAes128Sha256RsaOaep: {newAes128Sha256RsaOaepAsymmetric, newAes128Sha256RsaOaepSymmetric},
+	ua.SecurityPolicyURIAes256Sha256RsaPss:  {newAes256Sha256RsaPssAsymmetric, newAes256Sha256RsaPssSymmetric},
 }
 
 type policy struct {
diff --git a/uasc/secure_channel.go b/uasc/secure_channel.go
index 5ac31b2..20f31a0 100644
--- a/uasc/secure_channel.go
+++ b/uasc/secure_channel.go
@@ -69,7 +69,7 @@ func NewSecureChannel(endpoint string, c *uacp.Conn, cfg *Config) (*SecureChanne
 		return nil, fmt.Errorf("no secure channel config")
 	}
 
-	if cfg.SecurityPolicyURI != uapolicy.SecurityPolicyNone {
+	if cfg.SecurityPolicyURI != ua.SecurityPolicyURINone {
 		if cfg.SecurityMode == ua.MessageSecurityModeNone {
 			return nil, fmt.Errorf("invalid channel config: Security policy '%s' cannot be used with '%s'", cfg.SecurityPolicyURI, cfg.SecurityMode)
 		}
@@ -79,7 +79,7 @@ func NewSecureChannel(endpoint string, c *uacp.Conn, cfg *Config) (*SecureChanne
 	}
 
 	// Force the security mode to None if the policy is also None
-	if cfg.SecurityPolicyURI == uapolicy.SecurityPolicyNone {
+	if cfg.SecurityPolicyURI == ua.SecurityPolicyURINone {
 		cfg.SecurityMode = ua.MessageSecurityModeNone
 	}
 
diff --git a/uasc/secure_channel_crypto.go b/uasc/secure_channel_crypto.go
index 8e6493c..d97654c 100644
--- a/uasc/secure_channel_crypto.go
+++ b/uasc/secure_channel_crypto.go
@@ -167,8 +167,8 @@ func (s *SecureChannel) VerifySessionSignature(cert, nonce, signature []byte) er
 // EncryptUserPassword issues a new signature for the client to send in ActivateSessionRequest
 func (s *SecureChannel) EncryptUserPassword(policyURI, password string, cert, nonce []byte) ([]byte, string, error) {
 
-	if policyURI == uapolicy.SecurityPolicyNone {
-		return []byte(password), uapolicy.SecurityPolicyNone, nil
+	if policyURI == ua.SecurityPolicyURINone {
+		return []byte(password), "", nil
 	}
 
 	// If the User ID Token's policy was null, then default to the secure channel's policy
@@ -204,7 +204,7 @@ func (s *SecureChannel) EncryptUserPassword(policyURI, password string, cert, no
 // NewUserTokenSignature issues a new signature for the client to send in ActivateSessionRequest
 func (s *SecureChannel) NewUserTokenSignature(policyURI string, cert, nonce []byte) ([]byte, string, error) {
 
-	if policyURI == uapolicy.SecurityPolicyNone {
+	if policyURI == ua.SecurityPolicyURINone {
 		return nil, "", nil
 	}
 


Test patch:
diff --git a/uapolicy/securitypolicy_test.go b/uapolicy/securitypolicy_test.go
index 983a6b1..be26549 100644
--- a/uapolicy/securitypolicy_test.go
+++ b/uapolicy/securitypolicy_test.go
@@ -13,6 +13,8 @@ import (
 	"testing"
 
 	"github.com/pascaldekloe/goe/verify"
+
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestSupportedPolicies(t *testing.T) {
@@ -140,7 +142,7 @@ func TestEncryptionAlgorithms(t *testing.T) {
 
 			nonceLength := localAsymmetric.NonceLength()
 			localNonce, remoteNonce := makeNonce(nonceLength), makeNonce(nonceLength)
-			if nonceLength == 0 && uri != SecurityPolicyNone {
+			if nonceLength == 0 && uri != ua.SecurityPolicyURINone {
 				t.Fatalf("client nonce length zero")
 			}