Skip to content

Commit

Permalink
mrand
Browse files Browse the repository at this point in the history
seeded private-key

impl SignatureAlgorithm

cmt changes
  • Loading branch information
led0nk committed Feb 4, 2025
1 parent 83e81de commit 96172bb
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 62 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ require (
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.188.0 // indirect
Expand Down Expand Up @@ -370,11 +370,11 @@ require (
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.24.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1228,8 +1228,8 @@ golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -1385,8 +1385,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand All @@ -1395,8 +1395,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
80 changes: 57 additions & 23 deletions opcua_plugin/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,43 @@ func (g *OPCUAInput) orderEndpoints(
selectedAuthentication ua.UserTokenType,
) []*ua.EndpointDescription {

var highSecurityEndpoints, mediumSecurityEndpoints, lowSecurityEndpoints, noSecurityEndpoints []*ua.EndpointDescription
var (
signAndEncryptBasic256Sha256Endpoints []*ua.EndpointDescription
signBasic256Sha256Endpoints []*ua.EndpointDescription
signAndEncryptBasic256Endpoints []*ua.EndpointDescription
signBasic256Endpoints []*ua.EndpointDescription
signAndEncryptBasic128Rsa15Endpoints []*ua.EndpointDescription
signBasic128Rsa15Endpoints []*ua.EndpointDescription
noSecurityEndpoints []*ua.EndpointDescription
)

for _, endpoint := range endpoints {
if isUserTokenSupported(endpoint, selectedAuthentication) {
switch {
case isSignAndEncryptbasic256Sha256Endpoint(endpoint):
highSecurityEndpoints = append(highSecurityEndpoints, endpoint)
case isSignAndEncryptbasic256Endpoint(endpoint):
mediumSecurityEndpoints = append(mediumSecurityEndpoints, endpoint)
case isSignAndEncryptbasic128Rsa15Endpoint(endpoint):
lowSecurityEndpoints = append(lowSecurityEndpoints, endpoint)
case isSignAndEncryptBasic256Sha256Endpoint(endpoint):
signAndEncryptBasic256Sha256Endpoints = append(signAndEncryptBasic256Sha256Endpoints, endpoint)
case isSignBasic256Sha256Endpoint(endpoint):
signBasic256Sha256Endpoints = append(signBasic256Sha256Endpoints, endpoint)
case isSignAndEncryptBasic256Endpoint(endpoint):
signAndEncryptBasic256Endpoints = append(signAndEncryptBasic256Endpoints, endpoint)
case isSignBasic256Endpoint(endpoint):
signBasic256Endpoints = append(signBasic256Endpoints, endpoint)
case isSignAndEncryptBasic128Rsa15Endpoint(endpoint):
signAndEncryptBasic128Rsa15Endpoints = append(signAndEncryptBasic128Rsa15Endpoints, endpoint)
case isSignBasic128Rsa15Endpoint(endpoint):
signBasic128Rsa15Endpoints = append(signBasic128Rsa15Endpoints, endpoint)
case isNoSecurityEndpoint(endpoint):
noSecurityEndpoints = append(noSecurityEndpoints, endpoint)
}
}
}

// Append no security endpoints to the end of the high security endpoints.
orderedEndpoints := append(highSecurityEndpoints, mediumSecurityEndpoints...)
orderedEndpoints = append(orderedEndpoints, lowSecurityEndpoints...)
// Append medium security endpoints to the end of the high security endpoints.
orderedEndpoints := append(signAndEncryptBasic256Sha256Endpoints, signBasic256Sha256Endpoints...)
orderedEndpoints = append(orderedEndpoints, signAndEncryptBasic256Endpoints...)
orderedEndpoints = append(orderedEndpoints, signAndEncryptBasic128Rsa15Endpoints...)
orderedEndpoints = append(orderedEndpoints, signBasic256Endpoints...)
orderedEndpoints = append(orderedEndpoints, signBasic128Rsa15Endpoints...)
orderedEndpoints = append(orderedEndpoints, noSecurityEndpoints...)

return orderedEndpoints
Expand All @@ -51,26 +68,41 @@ func isUserTokenSupported(endpoint *ua.EndpointDescription, selectedAuth ua.User
return false
}

// isSignAndEncryptbasic256Sha256Endpoint checks if the endpoint is configured with SignAndEncrypt and Basic256Sha256 security.
func isSignAndEncryptbasic256Sha256Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeFromString("SignAndEncrypt") &&
endpoint.SecurityPolicyURI == "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
// isSignAndEncryptBasic256Sha256Endpoint checks if the endpoint is configured with SignAndEncrypt and Basic256Sha256 security.
func isSignAndEncryptBasic256Sha256Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeSignAndEncrypt &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic256Sha256
}

func isSignAndEncryptbasic256Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeFromString("SignAndEncrypt") &&
endpoint.SecurityPolicyURI == "http://opcfoundation.org/UA/SecurityPolicy#Basic256"
func isSignBasic256Sha256Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeSign &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic256Sha256
}

func isSignAndEncryptbasic128Rsa15Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeFromString("SignAndEncrypt") &&
endpoint.SecurityPolicyURI == "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"
func isSignAndEncryptBasic256Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeSignAndEncrypt &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic256
}

func isSignBasic256Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeSign &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic256
}

func isSignAndEncryptBasic128Rsa15Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeSignAndEncrypt &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic128Rsa15
}

func isSignBasic128Rsa15Endpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeSign &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic128Rsa15
}

// isNoSecurityEndpoint checks if the endpoint has no security configured.
func isNoSecurityEndpoint(endpoint *ua.EndpointDescription) bool {
return endpoint.SecurityMode == ua.MessageSecurityModeFromString("None") &&
endpoint.SecurityPolicyURI == "http://opcfoundation.org/UA/SecurityPolicy#None"
return endpoint.SecurityMode == ua.MessageSecurityModeNone &&
endpoint.SecurityPolicyURI == ua.SecurityPolicyURINone
}

// getEndpointIfExists searches within the provided endpoints for a suitable OPC UA endpoint.
Expand All @@ -94,7 +126,9 @@ func (g *OPCUAInput) getEndpointIfExists(
for _, userIdentity := range endpoint.UserIdentityTokens {

// Match the endpoint with the selected authentication type.
if selectedAuthentication == userIdentity.TokenType && endpoint.SecurityPolicyURI == "http://opcfoundation.org/UA/SecurityPolicy#"+securityPolicy && endpoint.SecurityMode == ua.MessageSecurityModeFromString(securityMode) {
if selectedAuthentication == userIdentity.TokenType &&
endpoint.SecurityPolicyURI == ua.FormatSecurityPolicyURI(securityPolicy) &&
endpoint.SecurityMode == ua.MessageSecurityModeFromString(securityMode) {

return endpoint, nil
}
Expand Down
60 changes: 41 additions & 19 deletions opcua_plugin/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
// **Why This Function is Needed:**
// - To dynamically generate client options that match the server’s security requirements.
// - To handle different authentication methods, such as anonymous or username/password-based logins.
// - To generate and include client certificates when using enhanced security policies like Basic256Sha256.
// - To generate and include client certificates when using enhanced security policies like Basic256Sha256/Basic256/Basic128Rsa15
func (g *OPCUAInput) GetOPCUAClientOptions(selectedEndpoint *ua.EndpointDescription, selectedAuthentication ua.UserTokenType) (opts []opcua.Option, err error) {
opts = append(opts, opcua.SecurityFromEndpoint(selectedEndpoint, selectedAuthentication))

Expand All @@ -37,31 +37,53 @@ func (g *OPCUAInput) GetOPCUAClientOptions(selectedEndpoint *ua.EndpointDescript
opts = append(opts, opcua.AuthUsername(g.Username, g.Password))
}

// Generate certificates if Basic256Sha256
if selectedEndpoint.SecurityPolicyURI == ua.SecurityPolicyURIBasic256Sha256 {
randomStr := randomString(8) // Generates an 8-character random string
clientName := "urn:benthos-umh-test:client-" + randomStr
certPEM, keyPEM, err := GenerateCert(clientName, 2048, 24*time.Hour*365*10)
if err != nil {
g.Log.Errorf("Failed to generate certificate: %v", err)
return nil, err
}
// Generate certificates if we don't connect without Security
if selectedEndpoint.SecurityPolicyURI != ua.SecurityPolicyURINone {
if g.cachedTLSCertificate == nil {
if g.CertificateSeed == "" {
// Generate an 8-character random string if no 'certificateSeed'
// provided by the user.
g.CertificateSeed = randomString(8)
}

// Convert PEM to X509 Certificate and RSA PrivateKey for in-memory use.
cert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
g.Log.Errorf("Failed to parse certificate: %v", err)
return nil, err
// Just use a random String to make the appearance in "trusted clients"
// more "unique". So the user is able to recognize the client in the
// servers UI.
clientNameUID := randomString(8)

clientName := "urn:benthos-umh:client-predefined-" + clientNameUID
certPEM, keyPEM, err := GenerateCertWithMode(clientName,
24*time.Hour*365*10,
g.SecurityMode,
g.SecurityPolicy,
g.CertificateSeed,
clientNameUID)
if err != nil {
g.Log.Errorf("Failed to generate certificate: %v", err)
return nil, err
}

// Convert PEM to X509 Certificate and RSA PrivateKey for in-memory use.
cert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
g.Log.Errorf("Failed to parse certificate: %v", err)
return nil, err
}
// cache the tls.Certificate for future calls
g.cachedTLSCertificate = &cert
g.Log.Infof("The clients certificate was created, to use an encrypted connection "+
"please proceed to the OPC-UA Server Configuration and trust either all clients "+
"or the clients certificate with the Application-URI: '%s", clientName)
}

pk, ok := cert.PrivateKey.(*rsa.PrivateKey)
pk, ok := g.cachedTLSCertificate.PrivateKey.(*rsa.PrivateKey)
if !ok {
g.Log.Errorf("Invalid private key type")
return nil, err
}

// Append the certificate and private key to the client options
opts = append(opts, opcua.PrivateKey(pk), opcua.Certificate(cert.Certificate[0]))
opts = append(opts, opcua.PrivateKey(pk), opcua.Certificate(g.cachedTLSCertificate.Certificate[0]))
}

opts = append(opts, opcua.SessionName("benthos-umh"))
Expand All @@ -70,7 +92,7 @@ func (g *OPCUAInput) GetOPCUAClientOptions(selectedEndpoint *ua.EndpointDescript
} else {
opts = append(opts, opcua.SessionTimeout(SessionTimeout))
}
opts = append(opts, opcua.ApplicationName("benthos-umh"))
opts = append(opts, opcua.ApplicationName("benthos-umh-predefined"))
//opts = append(opts, opcua.ApplicationURI("urn:benthos-umh"))
//opts = append(opts, opcua.ProductURI("urn:benthos-umh"))

Expand Down Expand Up @@ -533,7 +555,7 @@ func (g *OPCUAInput) connect(ctx context.Context) error {
if g.SecurityMode != "" && g.SecurityPolicy != "" {
c, err = g.connectWithSecurity(ctx, endpoints, selectedAuthentication)
if err != nil {
g.Log.Infof("error while connecting using securitymode %s, securityPolicy: %s. err:%v", g.SecurityMode, g.SecurityPolicy, err)
g.Log.Infof("error while connecting using securitymode '%s', securityPolicy: '%s'. err:%v", g.SecurityMode, g.SecurityPolicy, err)
return err
}

Expand Down
Loading

0 comments on commit 96172bb

Please sign in to comment.