1
votes

I am trying to connect my local Kepware OPC UA server with certificate. OPC UA server security info: enter image description here

In golang app , I use "github.com/gopcua/opcua/ua" opc client package. I create key pair and I give them path to variables and used them in config:

    opts := []opcua.Option{
            opcua.SecurityPolicy(relatedConnection.SecurityPolicy),
            opcua.SecurityModeString(relatedConnection.SecurityMode),
            opcua.CertificateFile(certFile),
            opcua.PrivateKeyFile(keyFile),
            opcua.AutoReconnect(true),
            opcua.ReconnectInterval(time.Second * 5),
            opcua.RequestTimeout(time.Second * 3),
        }

When security policy and mode are none, I can connect to the server without any problem. When I chose these security policies I don't know how to implement code or ssl key pair to connect server.

1
Do you have an X.509 certificate or do you want to generate it in the opcua client? - eglease
I want to generate it in the opcua client. - oakkose

1 Answers

0
votes

In order to use any security policy above None/None or to use a username/password login, you need to either have an existing X.509 certificate or generate one yourself.

Note, the certificate comes in two parts, a public certificate and a private key. You need both. The public certificate will be sent to the Kepware server while the private key will stay with the client and be used for encryption and decryption.

endpoints, err := opcua.GetEndpoints(context.Background(), cfg.Endpoint)
if err != nil {
    return nil, fmt.Errorf("OPC GetEndpoints: %w", err)
}

policy := ua.SecurityPolicyURINone // Replace this with a constant of your security policy
mode := ua.MessageSecurityModeNone // Replace this with a constant of your security mode
ep := opcua.SelectEndpoint(endpoints, policy, mode)

c, err := generateCert() // This is where you generate the certificate
if err != nil {
    return nil, fmt.Errorf("generateCert: %w", err)
}

pk, ok := c.PrivateKey.(*rsa.PrivateKey) // This is where you set the private key
if !ok {
    return nil, fmt.Errorf("invalid private key")
}

cert := c.Certificate[0]
opts := []opcua.Option{
    opcua.SecurityPolicy(policy),
    opcua.SecurityMode(mode),
    opcua.PrivateKey(pk), 
    opcua.Certificate(cert),  // Set the certificate for the OPC UA Client
    opcua.AuthUsername(cfg.Username, cfg.Password), // Use this if you are using username and password
    opcua.SecurityFromEndpoint(ep, ua.UserTokenTypeUserName),
    opcua.SessionTimeout(30 * time.Minute),
    opcua.AutoReconnect(true),
    opcua.ReconnectInterval(time.Second * 10),
    opcua.Lifetime(30 * time.Minute),
    opcua.RequestTimeout(3 * time.Second),
}

Use the following to generate the X.509 certificate.

func generateCert() (*tls.Certificate, error) {

    priv, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        return nil, fmt.Errorf("failed to generate private key: %s", err)
    }

    notBefore := time.Now()
    notAfter := notBefore.Add(365 * 24 * time.Hour) // 1 year

    serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    if err != nil {
        return nil, fmt.Errorf("failed to generate serial number: %s", err)
    }

    template := x509.Certificate{
        SerialNumber: serialNumber,
        Subject: pkix.Name{
            Organization: []string{"Test Client"},
        },
        NotBefore: notBefore,
        NotAfter:  notAfter,

        KeyUsage:              x509.KeyUsageContentCommitment | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment | x509.KeyUsageCertSign,
        ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
        BasicConstraintsValid: true,
    }

    host := "urn:testing:client"
    if ip := net.ParseIP(host); ip != nil {
        template.IPAddresses = append(template.IPAddresses, ip)
    } else {
        template.DNSNames = append(template.DNSNames, host)
    }
    if uri, err := url.Parse(host); err == nil {
        template.URIs = append(template.URIs, uri)
    }

    derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
    if err != nil {
        return nil, fmt.Errorf("failed to create certificate: %s", err)
    }

    certBuf := bytes.NewBuffer(nil)
    if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
        return nil, fmt.Errorf("failed to encode certificate: %s", err)
    }

    keyBuf := bytes.NewBuffer(nil)
    if err := pem.Encode(keyBuf, pemBlockForKey(priv)); err != nil {
        return nil, fmt.Errorf("failed to encode key: %s", err)
    }

    cert, err := tls.X509KeyPair(certBuf.Bytes(), keyBuf.Bytes())
    return &cert, err
}

Here is an example directly from the gopcua project: https://github.com/gopcua/opcua/blob/main/examples/crypto/generate_cert.go