Golang Opc UA Client Implementation with Certificate - go

I am trying to connect my local Kepware OPC UA server with certificate. OPC UA server security info:
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.

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

Related

Golang - TLS mutual authentication - Dump client certificates

I have TLS server with mutual authentication. I want to dump client certificates if handshake error. I use ErrorLog in http.Server struct, but this logger doesn't get the information about client certificates. I tried to use the VerifyConnection function in tls.Config struct, but it starts after the correct handshake. How can I dump the client certificates(wrong and corrects)?
You could dump the client certificates through tls Conn.ConnectionState after Conn.HandShake as long as the handshake of TLS is done.
Here are code snippets
config := tls.Config{
Certificates: []tls.Certificate{yourServerCert},
ClientAuth: tls.RequestClientCert,
InsecureSkipVerify: true,
}
listener, err := tls.Listen("tcp", "localhost:8080", &config)
if err != nil {
fmt.Println("server: listen err %+v \n", err)
return
}
conn, err := listener.Accept()
if err != nil {
fmt.Println("server: accept err %+v \n", err)
return
}
tlsConn, ok := conn.(*tls.Conn)
if !ok {
fmt.Println("server: invalid tls connection")
return
}
if err := tlsConn.Handshake(); err != nil {
fmt.Println("server: client handshake err %+v \n", err)
return
}
state := tlsConn.ConnectionState()
for _, v := range state.PeerCertificates {
fmt.Printf("server: remote client cert %+v \n", v)
}

SupportsCertificate throws no mutually supported protocol versions for x509Certificate extracted from pfx

With the following code, I'm creating a TLS cert
func loadCert() tls.Certificate {
b, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatalln("failed to find / open cert file", err)
}
p, c, err1 := pkcs12.Decode(b, password)
if err1 != nil {
log.Fatalln("Failed to read cert content", err1)
}
tlsCert := tls.Certificate{
PrivateKey: p,
Leaf: c,
}
return tlsCert
}
And then with NewCertPool(), I am adding this to the pool
func makeRequest(){
// Loading cert
cert := loadCert()
caCertPool := x509.NewCertPool()
caCertPool.AddCert(cert.Leaf)
tConfig := &tls.Config{
RootCAs: caCertPool,
PreferServerCipherSuites: true,
InsecureSkipVerify: false,
Certificates: []tls.Certificate{cert},
}
tr := &http.Transport{
TLSClientConfig: tConfig,
}
client := http.Client{
Transport: tr,
}
request, reqError := http.NewRequest("POST", url, bytes.NewBuffer(dataToPost))
if reqError != nil {
log.Fatalln(" failed with error", reqError)
}
request.Header.Set("Accept", "*/*")
request.Header.Set("Connection", "keep-alive")
request.Header.Add("Content-Type", "text/xml;charset=UTF-8")
res, dErr := client.Do(request)
if dErr != nil {
log.Fatalln("Failed to make request", dErr)
} else {
log.Println(res.StatusCode)
}
}
SupportCertificate throws
no mutually supported protocol versions
Same certificate works just fine with tools like Postman. If I remove the check with this supportCertificate function and try to make POST call server reports that client certificate is not found, despite adding it in the transport above . And on the client side I get the error that connection has been reset and
x509: certificate signed by unknown authority
To remedy this I tried using SystemCertPool(), after which code fails to execute on windows with error SystemCertPool not found. On Docker or on VM it fails with
connection has been reset by peer
I'm really at loss here what I am missing, would appreciate some help on this.
I have already scoured through really helpful posts on how TLS can be configured and how CA certs could be added to the new pool. But I still seem to be missing something.
You can try to use your http.Client with configured tls.Config, where you re-defined Root CA. For example:
http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caPool,
},
},
where caPool can be obtained as
caPool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("SystemCertPool() error: %v", err)
}
if caPool == nil {
caPool = x509.NewCertPool()
}
certs, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read CA from file '%q': %v", path, err)
}
if ok := caPool.AppendCertsFromPEM(certs); !ok {
return nil, fmt.Errorf("failed to append local CA to root cert pool")
}

Parsing X509 certificate in Go

I am having the following function, which reads an X509 certificate.
certCerFile,err := os.Open("certificate.pem")
if err != nil {
log.Fatal(err)
}
derBytes := make([]byte,1000)
count,err:=certCerFile.Read(derBytes)
if err != nil {
log.Fatal(err)
}
certCerFile.Close()
// trim the bytes to actual length in call
cert,err := x509.ParseCertificate(derBytes[0:count])
if err != nil {
log.Fatal(err)
}
fmt.Printf("Name %s\n", cert.Subject.CommonName)
fmt.Printf("Not before %s\n", cert.NotBefore.String())
fmt.Printf("Not after %s\n", cert.NotAfter.String())
I face the following error:
asn1: structure error: tags don't match (16 vs {class:0 tag:13 length:45 isCompound:true}) {optional:false explicit:false application:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} certificate #2
That's how I generate X509:
random := rand.Reader
var key rsa.PrivateKey
loadKey("private.key",&key)
now:= time.Now()
then := now.Add(60 * 60 * 24 * 365 * 1000 * 1000 * 1000)
template:= x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "borscht.com",
Organization: []string{"Borscht Systems AG"},
},
NotBefore:now,
NotAfter:then,
SubjectKeyId: []byte{1,2,3,4},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
BasicConstraintsValid:true,
IsCA:true,
DNSNames:[]string{"borscht.com","localhost"},
}
derBytes,err:=x509.CreateCertificate(random, &template, &template,&key.PublicKey,&key)
if err != nil {
log.Fatal(err)
}
certCerFile,err :=os.Create("certificate.cer")
if err != nil {
log.Fatal(err)
}
certCerFile.Write(derBytes)
certCerFile.Close()
certPemFile, err := os.Create("certificate.pem")
if err != nil {
log.Fatal(err)
}
I just don't understand what might be wrong.
I made a mistake myself. Parse pem instead of cer file. Replaced and everything is fine

SSH Handshake complains about missing host key

I'm trying to connect to a remote host and check if a file exist
At this stage I'm trying just to connect but I'm getting an error:
2017/08/01 18:16:39 unable to connect: ssh: handshake failed: ssh: required host key was nil
I've tried to find out if others had issues as mine but I just couldn't find.
I understand that I need to check the knowns_hosts somehow in the process but I just can't figure out how...
var hostKey ssh.PublicKey
// A public key may be used to authenticate against the remote
// server by using an unencrypted PEM-encoded private key file.
//
// If you have an encrypted private key, the crypto/x509 package
// can be used to decrypt it.
key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
}
I would suggest to use knownhosts subpackage
import knownhosts "golang.org/x/crypto/ssh/knownhosts"
...
hostKeyCallback, err := knownhosts.New("/Users/user/.ssh/known_hosts")
if err != nil {
log.Fatal(err)
}
...
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: hostKeyCallback,
}
So that you avoid parsing known_hosts yourself...
hth,
Here what you are looking for:
func getHostKey(host string) (ssh.PublicKey, error) {
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
return nil, errors.New(fmt.Sprintf("error parsing %q: %v", fields[2], err))
}
break
}
}
if hostKey == nil {
return nil, errors.New(fmt.Sprintf("no hostkey for %s", host))
}
return hostKey, nil
}
Then replace your hostKey definition line with
hostKey, err := getHostKey("host.com")
if err != nil {
log.Fatal(err)
}
For more information on the subject:
official sample where I took parts of the code from
why a hostKey is necessary now
EDIT:
Also check out Anton's answer below about the golang.org/x/crypto/ssh/knownhosts package.

failed to complete security handshake on grpc?

I've already changed many times the port number on the server and client, but the server always get the incorrect port number.
when I execute the client the server will log this:
2017/05/07 15:06:07 grpc: Server.Serve failed to complete security handshake from "127.0.0.1:32763": remote error: tls: bad certificate
and on the client side, i got this:
2017/05/07 15:06:07 Failed to dial localhost:8070: connection error: desc = "transport: x509: certificate is not valid for any names, but wanted to match localhost:8070"; please retry.
rpc error: code = Internal desc = connection error: desc = "transport: x509: certificate is not valid for any names, but wanted to match localhost:8070"
I have this code for the server.go
func serve() {
addr := "localhost:8070"
crt, key := certificate.CreatePemKey()
certificate, err := tls.X509KeyPair(crt, key)
if err != nil {
fmt.Println(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("F:/GIAG3.crt")
if err != nil {
fmt.Println(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
fmt.Println("unable to append certificate")
}
lis, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println("could not list on %s: %s", addr, err)
}
// Create the TLS credentials
creds := credentials.NewTLS(&tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{certificate},
ClientCAs: certPool,
})
srv := grpc.NewServer(grpc.Creds(creds))
pb.RegisterPingerServer(srv, &server{})
if err := srv.Serve(lis); err != nil {
fmt.Println("grpc serve error: %s", err)
}
}
and this is for the client.go
func testDial2() {
addr := "localhost:8070"
crt, key := certificate.CreatePemKey()
certificate, err := tls.X509KeyPair(crt, key)
if err != nil {
fmt.Println(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("F:/GIAG3.crt")
if err != nil {
fmt.Println(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
fmt.Println("unable to append certificate")
}
creds := credentials.NewTLS(&tls.Config{
ServerName: addr,
Certificates: []tls.Certificate{certificate},
RootCAs: certPool,
})
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
if err != nil {
fmt.Println(err)
}
defer conn.Close()
c := pb.NewPingerClient(conn)
r, err := c.Ping(context.Background(), &pb.Payload{Message: "Ping"})
if err != nil {
fmt.Println(err)
}
log.Printf("%s", r.Message)
}
this is for the CreatePemKey, it is based on this example https://golang.org/src/crypto/tls/generate_cert.go
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
func CreatePemKey() (certpem, keypem []byte) {
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
notBefore := time.Now()
notAfter := notBefore.AddDate(1, 0, 0)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
// template.IPAddresses = append(template.IPAddresses, net.ParseIP("localhost"))
template.IsCA = true
derbytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
certpem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derbytes})
keypem = pem.EncodeToMemory(pemBlockForKey(priv))
return certpem, keypem
}
BTW the GIAG3.crt is came from here https://pki.goog/
Please help me, thanks
If your server certificate doesn't have the domain definition and it's not signed by GIAG3 (like your example), you should add InsecureSkipVerify (this allow you skip the validation of the server name and the server certificate) in the client configuration, that will fix the issue with the invalid name.
creds := credentials.NewTLS(&tls.Config{
ServerName: addr,
Certificates: []tls.Certificate{certificate},
RootCAs: certPool,
InsecureSkipVerify: true,
})
But you'll have another problem, because the client is using a self-signed certificate, and the server required a certificate signed by GIAG3 for the authentication (tls.RequireAndVerifyClientCert), so you have some options with this,
you use a certificate signed by GIAG3 for the client.
reduce the authentication type to tls.RequireAnyClientCert, this allow you use any certificate at the moment of the auth (it could be or not signed by GIAG3), the client just need to use any certificate when it's connected.
remove the client authentication with certificate.
If you found this log in journalctl -u docker:
mai 29 10:33:04 ca275nt dockerd[1523]: time="2019-05-29T10:33:04.454362399-03:00" level=warning msg="grpc: Server.Serve failed to complete security handshake from \"192.168.0.45:58392\": remote error: tls: bad certificate" module=grpc
It can be related with docker swarm requests from another swarm cluster node host trying to connect to docker swarm master node with a invalid token.
You can discover the invalid cluster node hostname with:
nslookup 192.168.0.45
45.0.168.192.in-addr.arpa name = hostabc.domain.com.
45.0.168.192.in-addr.arpa name = hostabc.
Authoritative answers can be found from:
By running a docker swarm leave on the reported host, resolve the issue.
I know it's not related with the main question, but I was searching for:
docker "grpc: Server.Serve failed to complete security handshake from" and this was the first question. then I think it's useful to put the troubleshooting here to save some time to others.

Resources