Skip to content

Commit

Permalink
add e2e encryption tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddin-619 committed May 30, 2023
1 parent b993e02 commit 0a7a842
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 0 deletions.
159 changes: 159 additions & 0 deletions uasc/secure_channel_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package uasc

import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"math"
"testing"
"time"

"github.com/gopcua/opcua/id"
"github.com/gopcua/opcua/ua"
"github.com/gopcua/opcua/uapolicy"
"github.com/gopcua/opcua/uatest"

"github.com/pascaldekloe/goe/verify"
)
Expand Down Expand Up @@ -145,3 +152,155 @@ func TestNewRequestMessage(t *testing.T) {
})
}
}

func TestSignAndEncryptVerifyAndDecrypt(t *testing.T) {

buildSecPolicy := func(bits int, uri string) *uapolicy.EncryptionAlgorithm {
certb, keyb := uatest.Generate_cert("localhost", bits)
block, _ := pem.Decode(keyb)
pk, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
fmt.Printf("err: %v\n", err)
}
certblock, _ := pem.Decode(certb)
remoteX509Cert, err := x509.ParseCertificate(certblock.Bytes)
if err != nil {
fmt.Printf("err: %v\n", err)
}
remoteKey := remoteX509Cert.PublicKey.(*rsa.PublicKey)
alg, _ := uapolicy.Asymmetric(uri, pk, remoteKey)
return alg
}

getConfig := func(uri string) *Config {
if uri == ua.SecurityPolicyURINone {
return &Config{SecurityMode: ua.MessageSecurityModeNone}
}
return &Config{SecurityMode: ua.MessageSecurityModeSignAndEncrypt}
}

tests := []struct {
name string
c *channelInstance
m *Message
b []byte
}{}

for _, uri := range ua.SecurityPolicyURIs {
for i, keyLength := range []int{2048, 4096} {
if i == 1 && (uri == ua.SecurityPolicyURIBasic128Rsa15 || uri == ua.SecurityPolicyURIBasic256) {
continue
}
tests = append(tests, struct {
name string
c *channelInstance
m *Message
b []byte
}{fmt.Sprintf("encrypt/decrypt: bits: %d uri: %s", keyLength, uri),
&channelInstance{
sc: &SecureChannel{cfg: getConfig(uri)},
algo: buildSecPolicy(keyLength, uri),
},
&Message{
MessageHeader: &MessageHeader{
Header: &Header{
MessageType: MessageTypeOpenSecureChannel,
ChunkType: ChunkTypeFinal,
},
AsymmetricSecurityHeader: &AsymmetricSecurityHeader{
SecurityPolicyURI: "http://gopcua.example/OPCUA/SecurityPolicy#Foo",
},
SequenceHeader: &SequenceHeader{
SequenceNumber: 1,
RequestID: 1,
},
},
},
[]byte{ // OpenSecureChannelRequest
// Message Header
// MessageType: OPN
0x4f, 0x50, 0x4e,
// Chunk Type: Final
0x46,
// MessageSize: 131
0x8E, 0x00, 0x00, 0x00,
// SecureChannelID: 0
0x00, 0x00, 0x00, 0x00,
// AsymmetricSecurityHeader
// SecurityPolicyURILength
0x2e, 0x00, 0x00, 0x00,
// SecurityPolicyURI
0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67,
0x6f, 0x70, 0x63, 0x75, 0x61, 0x2e, 0x65, 0x78,
0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x4f, 0x50,
0x43, 0x55, 0x41, 0x2f, 0x53, 0x65, 0x63, 0x75,
0x72, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x23, 0x46, 0x6f, 0x6f,
// SenderCertificate
0xff, 0xff, 0xff, 0xff,
// ReceiverCertificateThumbprint
0xff, 0xff, 0xff, 0xff,
// Sequence Header
// SequenceNumber
0x01, 0x00, 0x00, 0x00,
// RequestID
0x01, 0x00, 0x00, 0x00,
// TypeID
0x01, 0x00, 0xbe, 0x01,

// RequestHeader
// - AuthenticationToken
0x00, 0x00,
// - Timestamp
0x00, 0x98, 0x67, 0xdd, 0xfd, 0x30, 0xd4, 0x01,
// - RequestHandle
0x01, 0x00, 0x00, 0x00,
// - ReturnDiagnostics
0xff, 0x03, 0x00, 0x00,
// - AuditEntry
0xff, 0xff, 0xff, 0xff,
// - TimeoutHint
0x00, 0x00, 0x00, 0x00,
// - AdditionalHeader
// - TypeID
0x00, 0x00,
// - EncodingMask
0x00,
// ClientProtocolVersion
0x00, 0x00, 0x00, 0x00,
// SecurityTokenRequestType
0x00, 0x00, 0x00, 0x00,
// MessageSecurityMode
0x01, 0x00, 0x00, 0x00,
// ClientNonce
0xff, 0xff, 0xff, 0xff,
// RequestedLifetime
0x80, 0x8d, 0x5b, 0x00,
}})

}
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cipher, err := tt.c.signAndEncrypt(tt.m, tt.b)
if err != nil {
t.Fatalf("error: message encrypt: %v", err)
}

m := new(MessageChunk)
if _, err := m.Decode(cipher); err != nil {
t.Fatalf("error: message decode: %v", err)
}
plain, err := tt.c.verifyAndDecrypt(m, cipher)
if err != nil {
t.Fatalf("error: message decrypt: %v", err)
}

headerLength := 12 + m.AsymmetricSecurityHeader.Len()
if got, want := plain, tt.b[headerLength:]; !bytes.Equal(got, want) {
t.Fatalf("got bytes %v want %v", got, want)
}
})
}
}
111 changes: 111 additions & 0 deletions uatest/generate_cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Generate a self-signed X.509 certificate for a TLS server. Outputs to
// 'cert.pem' and 'key.pem' and will overwrite existing files.

// Modified by the Gopcua Authors for use in creating an OPC-UA compliant client certificate

package uatest

import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"net/url"
"os"
"strings"
"time"
)

func Generate_cert(host string, rsaBits int) (cert, key []byte) {

if len(host) == 0 {
log.Fatalf("Missing required host parameter")
}
if rsaBits == 0 {
rsaBits = 2048
}

priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
log.Fatalf("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 {
log.Fatalf("failed to generate serial number: %s", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Gopcua 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,
}

hosts := strings.Split(host, ",")
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
if uri, err := url.Parse(h); err == nil {
template.URIs = append(template.URIs, uri)
}
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
log.Fatalf("Failed to create certificate: %s", err)
}

return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), pem.EncodeToMemory(pemBlockForKey(priv))

}

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
}
}

0 comments on commit 0a7a842

Please sign in to comment.