From 7f8b1d19c6d178c067fe07896c672899eb94e304 Mon Sep 17 00:00:00 2001 From: Jack Chen Date: Thu, 24 Aug 2023 12:17:33 +0800 Subject: [PATCH 1/2] feat: add support for AuthPrivateKey NewUserTokenSignature should use the RSA key associated with AuthCertificate to sign the user token signature. Tested with Prosys OPC UA Simulation Server. Closes #671 --- config.go | 10 ++++++++++ config_test.go | 11 +++++++++++ uasc/config.go | 4 ++++ uasc/secure_channel_crypto.go | 2 +- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index ff42433d..cefedd5c 100644 --- a/config.go +++ b/config.go @@ -467,6 +467,16 @@ func AuthCertificate(cert []byte) Option { } } +// AuthPrivateKey sets the client's authentication RSA private key +// Note: PolicyID still needs to be set outside of this method, typically through +// the SecurityFromEndpoint() Option +func AuthPrivateKey(key *rsa.PrivateKey) Option { + return func(cfg *Config) error { + cfg.sechan.UserKey = key + return nil + } +} + // 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 diff --git a/config_test.go b/config_test.go index 602db96a..a427efb2 100644 --- a/config_test.go +++ b/config_test.go @@ -218,6 +218,17 @@ func TestOptions(t *testing.T) { }(), }, }, + { + name: `AuthPrivateKey()`, + opt: AuthPrivateKey(cert.PrivateKey.(*rsa.PrivateKey)), + cfg: &Config{ + sechan: func() *uasc.Config { + c := DefaultClientConfig() + c.UserKey = cert.PrivateKey.(*rsa.PrivateKey) + return c + }(), + }, + }, { name: `AuthIssuedToken()`, opt: AuthIssuedToken([]byte("a")), diff --git a/uasc/config.go b/uasc/config.go index 7908037b..2e86ee64 100644 --- a/uasc/config.go +++ b/uasc/config.go @@ -33,6 +33,10 @@ type Config struct { // messages. It is the key associated with Certificate LocalKey *rsa.PrivateKey + // UserKey is a RSA Private Key which will be used to sign the UserTokenSignature. + // It is the key associated with AuthCertificate + UserKey *rsa.PrivateKey + // Thumbprint is the thumbprint of the X.509 v3 Certificate assigned to the receiving // application Instance. // The thumbprint is the CertificateDigest of the DER encoded form of the diff --git a/uasc/secure_channel_crypto.go b/uasc/secure_channel_crypto.go index d3cd98bd..16dbc856 100644 --- a/uasc/secure_channel_crypto.go +++ b/uasc/secure_channel_crypto.go @@ -111,7 +111,7 @@ func (s *SecureChannel) NewUserTokenSignature(policyURI string, cert, nonce []by } remoteKey := remoteX509Cert.PublicKey.(*rsa.PublicKey) - enc, err := uapolicy.Asymmetric(policyURI, s.cfg.LocalKey, remoteKey) + enc, err := uapolicy.Asymmetric(policyURI, s.cfg.UserKey, remoteKey) if err != nil { return nil, "", err } From 27c7e611fe5d99fda858908e522dedff08ea034d Mon Sep 17 00:00:00 2001 From: Jack Chen Date: Fri, 1 Sep 2023 12:32:11 +0800 Subject: [PATCH 2/2] issue-671 Update example crypto.go --- examples/crypto/crypto.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/examples/crypto/crypto.go b/examples/crypto/crypto.go index 76b92d93..3ac0677c 100644 --- a/examples/crypto/crypto.go +++ b/examples/crypto/crypto.go @@ -125,6 +125,7 @@ func clientOptsFromFlags(endpoints []*ua.EndpointDescription) []opcua.Option { } var cert []byte + var privateKey *rsa.PrivateKey if *gencert || (*certfile != "" && *keyfile != "") { if *gencert { certPEM, keyPEM, err := uatest.GenerateCert(*appuri, 2048, 24*time.Hour) @@ -148,6 +149,7 @@ func clientOptsFromFlags(endpoints []*ua.EndpointDescription) []opcua.Option { log.Fatalf("Invalid private key") } cert = c.Certificate[0] + privateKey = pk opts = append(opts, opcua.PrivateKey(pk), opcua.Certificate(cert)) } } @@ -167,8 +169,8 @@ func clientOptsFromFlags(endpoints []*ua.EndpointDescription) []opcua.Option { } // Select the most appropriate authentication mode from server capabilities and user input - authMode, authOption := authFromFlags(cert) - opts = append(opts, authOption) + authMode, authOptions := authFromFlags(cert, privateKey) + opts = append(opts, authOptions...) var secMode ua.MessageSecurityMode switch strings.ToLower(*mode) { @@ -246,15 +248,15 @@ func clientOptsFromFlags(endpoints []*ua.EndpointDescription) []opcua.Option { return opts } -func authFromFlags(cert []byte) (ua.UserTokenType, opcua.Option) { +func authFromFlags(cert []byte, pk *rsa.PrivateKey) (ua.UserTokenType, []opcua.Option) { var err error var authMode ua.UserTokenType - var authOption opcua.Option + var authOptions []opcua.Option switch strings.ToLower(*auth) { case "anonymous": authMode = ua.UserTokenTypeAnonymous - authOption = opcua.AuthAnonymous() + authOptions = append(authOptions, opcua.AuthAnonymous()) case "username": authMode = ua.UserTokenTypeUserName @@ -284,25 +286,28 @@ func authFromFlags(cert []byte) (ua.UserTokenType, opcua.Option) { *password = string(passInput) fmt.Print("\n") } - authOption = opcua.AuthUsername(*username, *password) + authOptions = append(authOptions, opcua.AuthUsername(*username, *password)) case "certificate": authMode = ua.UserTokenTypeCertificate - authOption = opcua.AuthCertificate(cert) + // Note: You should still use these two Config options to load the auth certificate and private key + // separately from the secure channel configuration even if the same certificate is used for both purposes + authOptions = append(authOptions, opcua.AuthCertificate(cert)) + authOptions = append(authOptions, opcua.AuthPrivateKey(pk)) case "issuedtoken": // todo: this is unsupported, fail here or fail in the opcua package? authMode = ua.UserTokenTypeIssuedToken - authOption = opcua.AuthIssuedToken([]byte(nil)) + authOptions = append(authOptions, opcua.AuthIssuedToken([]byte(nil))) default: log.Printf("unknown auth-mode, defaulting to Anonymous") authMode = ua.UserTokenTypeAnonymous - authOption = opcua.AuthAnonymous() + authOptions = append(authOptions, opcua.AuthAnonymous()) } - return authMode, authOption + return authMode, authOptions } func validateEndpointConfig(endpoints []*ua.EndpointDescription, secPolicy string, secMode ua.MessageSecurityMode, authMode ua.UserTokenType) error {