// Copyright 2018-2019 opcua authors. All rights reserved. // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file. package opcua import ( "crypto/rsa" "crypto/x509" "encoding/pem" "io/ioutil" "log" "math/rand" "strings" "time" "github.com/gopcua/opcua/errors" "github.com/gopcua/opcua/ua" "github.com/gopcua/opcua/uapolicy" "github.com/gopcua/opcua/uasc" ) // DefaultClientConfig returns the default configuration for a client // to establish a secure channel. func DefaultClientConfig() *uasc.Config { return &uasc.Config{ SecurityPolicyURI: ua.SecurityPolicyURINone, SecurityMode: ua.MessageSecurityModeNone, Lifetime: uint32(time.Hour / time.Millisecond), RequestTimeout: 10 * time.Second, } } // DefaultSessionConfig returns the default configuration for a client // to establish a session. func DefaultSessionConfig() *uasc.SessionConfig { return &uasc.SessionConfig{ SessionTimeout: 20 * time.Minute, ClientDescription: &ua.ApplicationDescription{ ApplicationURI: "urn:gopcua:client", ProductURI: "urn:gopcua", ApplicationName: &ua.LocalizedText{Text: "gopcua - OPC UA implementation in Go"}, ApplicationType: ua.ApplicationTypeClient, }, LocaleIDs: []string{"en-us"}, UserTokenSignature: &ua.SignatureData{}, } } // ApplyConfig applies the config options to the default configuration. // todo(fs): Can we find a better name? func ApplyConfig(opts ...Option) (*uasc.Config, *uasc.SessionConfig) { c := DefaultClientConfig() sc := DefaultSessionConfig() for _, opt := range opts { opt(c, sc) } return c, sc } // Option is an option function type to modify the configuration. type Option func(*uasc.Config, *uasc.SessionConfig) // ApplicationName sets the application name in the session configuration. func ApplicationName(s string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { sc.ClientDescription.ApplicationName = &ua.LocalizedText{Text: s} } } // ApplicationURI sets the application uri in the session configuration. func ApplicationURI(s string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { sc.ClientDescription.ApplicationURI = s } } // Lifetime sets the lifetime of the secure channel in milliseconds. func Lifetime(d time.Duration) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.Lifetime = uint32(d / time.Millisecond) } } // Locales sets the locales in the session configuration. func Locales(locale ...string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { sc.LocaleIDs = locale } } // ProductURI sets the product uri in the session configuration. func ProductURI(s string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { sc.ClientDescription.ProductURI = s } } // RandomRequestID assigns a random initial request id. func RandomRequestID() Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.RequestID = uint32(rand.Int31()) } } // RemoteCertificate sets the server certificate. func RemoteCertificate(cert []byte) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.RemoteCertificate = cert } } // RemoteCertificateFile sets the server certificate from the file // in PEM or DER encoding. func RemoteCertificateFile(filename string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { cert, err := loadCertificate(filename) if err != nil { log.Fatal(err) } c.RemoteCertificate = cert } } // SecurityMode sets the security mode for the secure channel. func SecurityMode(m ua.MessageSecurityMode) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.SecurityMode = m } } // SecurityModeString sets the security mode for the secure channel. // Valid values are "None", "Sign", and "SignAndEncrypt". func SecurityModeString(s string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.SecurityMode = ua.MessageSecurityModeFromString(s) } } // SecurityPolicy sets the security policy uri for the secure channel. func SecurityPolicy(s string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.SecurityPolicyURI = ua.FormatSecurityPolicyURI(s) } } // SessionTimeout sets the timeout in the session configuration. func SessionTimeout(d time.Duration) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { sc.SessionTimeout = d } } // PrivateKey sets the RSA private key in the secure channel configuration. func PrivateKey(key *rsa.PrivateKey) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.LocalKey = key } } // PrivateKeyFile sets the RSA private key in the secure channel configuration // from a PEM or DER encoded file. func PrivateKeyFile(filename string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if filename == "" { return } key, err := loadPrivateKey(filename) if err != nil { log.Fatal(err) } c.LocalKey = key } } func loadPrivateKey(filename string) (*rsa.PrivateKey, error) { b, err := ioutil.ReadFile(filename) if err != nil { return nil, errors.Errorf("Failed to load private key: %s", err) } derBytes := b if strings.HasSuffix(filename, ".pem") { block, _ := pem.Decode(b) if block == nil || block.Type != "RSA PRIVATE KEY" { return nil, errors.Errorf("Failed to decode PEM block with private key") } derBytes = block.Bytes } pk, err := x509.ParsePKCS1PrivateKey(derBytes) if err != nil { return nil, errors.Errorf("Failed to parse private key: %s", err) } return pk, nil } // Certificate sets the client X509 certificate in the secure channel configuration. // It also detects and sets the ApplicationURI from the URI within the certificate. func Certificate(cert []byte) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { setCertificate(cert, c, sc) } } // Certificate sets the client X509 certificate in the secure channel configuration // from the PEM or DER encoded file. It also detects and sets the ApplicationURI // from the URI within the certificate. func CertificateFile(filename string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if filename == "" { return } cert, err := loadCertificate(filename) if err != nil { log.Fatal(err) } setCertificate(cert, c, sc) } } func loadCertificate(filename string) ([]byte, error) { b, err := ioutil.ReadFile(filename) if err != nil { return nil, errors.Errorf("Failed to load certificate: %s", err) } if !strings.HasSuffix(filename, ".pem") { return b, nil } block, _ := pem.Decode(b) if block == nil || block.Type != "CERTIFICATE" { return nil, errors.Errorf("Failed to decode PEM block with certificate") } return block.Bytes, nil } func setCertificate(cert []byte, c *uasc.Config, sc *uasc.SessionConfig) { c.Certificate = cert // Extract the application URI from the certificate. x509cert, err := x509.ParseCertificate(cert) if err != nil { log.Fatalf("Failed to parse certificate: %s", err) return } if len(x509cert.URIs) == 0 { return } appURI := x509cert.URIs[0].String() if appURI == "" { return } sc.ClientDescription.ApplicationURI = appURI } // SecurityFromEndpoint sets the server-related security parameters from // a chosen endpoint (received from GetEndpoints()) func SecurityFromEndpoint(ep *ua.EndpointDescription, authType ua.UserTokenType) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.SecurityPolicyURI = ep.SecurityPolicyURI c.SecurityMode = ep.SecurityMode c.RemoteCertificate = ep.ServerCertificate c.Thumbprint = uapolicy.Thumbprint(ep.ServerCertificate) for _, t := range ep.UserIdentityTokens { if t.TokenType != authType { continue } if sc.UserIdentityToken == nil { switch authType { case ua.UserTokenTypeAnonymous: sc.UserIdentityToken = &ua.AnonymousIdentityToken{} case ua.UserTokenTypeUserName: sc.UserIdentityToken = &ua.UserNameIdentityToken{} case ua.UserTokenTypeCertificate: sc.UserIdentityToken = &ua.X509IdentityToken{} case ua.UserTokenTypeIssuedToken: sc.UserIdentityToken = &ua.IssuedIdentityToken{} } } setPolicyID(sc.UserIdentityToken, t.PolicyID) sc.AuthPolicyURI = t.SecurityPolicyURI return } if sc.UserIdentityToken == nil { sc.UserIdentityToken = &ua.AnonymousIdentityToken{PolicyID: defaultAnonymousPolicyID} sc.AuthPolicyURI = ua.SecurityPolicyURINone } } } func setPolicyID(t interface{}, policy string) { switch tok := t.(type) { case *ua.AnonymousIdentityToken: tok.PolicyID = policy case *ua.UserNameIdentityToken: tok.PolicyID = policy case *ua.X509IdentityToken: tok.PolicyID = policy case *ua.IssuedIdentityToken: tok.PolicyID = policy } } // AuthPolicyID sets the policy ID of the user identity token // Note: This should only be called if you know the exact policy ID the server is expecting. // Most callers should use SecurityFromEndpoint as it automatically finds the policyID // todo(fs): Should we make 'policy' an option to the other // todo(fs): AuthXXX methods since this approach requires context // todo(fs): and ordering? func AuthPolicyID(policy string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if sc.UserIdentityToken == nil { log.Printf("policy ID needs to be set after the policy type is chosen, no changes made. Call SecurityFromEndpoint() or an AuthXXX() option first") return } setPolicyID(sc.UserIdentityToken, policy) } } // AuthAnonymous sets the client's authentication X509 certificate // Note: PolicyID still needs to be set outside of this method, typically through // the SecurityFromEndpoint() Option func AuthAnonymous() Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if sc.UserIdentityToken == nil { sc.UserIdentityToken = &ua.AnonymousIdentityToken{} } _, ok := sc.UserIdentityToken.(*ua.AnonymousIdentityToken) if !ok { // todo(fs): should we Fatal here? log.Printf("non-anonymous authentication already configured, ignoring") return } } } // AuthUsername sets the client's authentication username and password // Note: PolicyID still needs to be set outside of this method, typically through // the SecurityFromEndpoint() Option func AuthUsername(user, pass string) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if sc.UserIdentityToken == nil { sc.UserIdentityToken = &ua.UserNameIdentityToken{} } t, ok := sc.UserIdentityToken.(*ua.UserNameIdentityToken) if !ok { // todo(fs): should we Fatal here? log.Printf("non-username authentication already configured, ignoring") return } t.UserName = user sc.AuthPassword = pass } } // AuthCertificate sets the client's authentication X509 certificate // Note: PolicyID still needs to be set outside of this method, typically through // the SecurityFromEndpoint() Option func AuthCertificate(cert []byte) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if sc.UserIdentityToken == nil { sc.UserIdentityToken = &ua.X509IdentityToken{} } t, ok := sc.UserIdentityToken.(*ua.X509IdentityToken) if !ok { // todo(fs): should we Fatal here? log.Printf("non-certificate authentication already configured, ignoring") return } t.CertificateData = cert } } // AuthIssuedToken sets the client's authentication data based on an externally-issued token // Note: PolicyID still needs to be set outside of this method, typically through // the SecurityFromEndpoint() Option func AuthIssuedToken(tokenData []byte) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { if sc.UserIdentityToken == nil { sc.UserIdentityToken = &ua.IssuedIdentityToken{} } t, ok := sc.UserIdentityToken.(*ua.IssuedIdentityToken) if !ok { log.Printf("non-issued token authentication already configured, ignoring") return } // todo(dw): not correct; need to read spec t.TokenData = tokenData } } // RequestTimeout sets the timeout for all requests over SecureChannel func RequestTimeout(t time.Duration) Option { return func(c *uasc.Config, sc *uasc.SessionConfig) { c.RequestTimeout = t } }