Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for AuthPrivateKey #681

Merged
merged 2 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down
25 changes: 15 additions & 10 deletions examples/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))
}
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions uasc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion uasc/secure_channel_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down