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