Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt tests to work with open62541 v1.4.7 and adapt login tutorial #33

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Open62541"
uuid = "e9b70463-8ccb-4e30-a2e2-0d1ec8db6536"
authors = ["Martin Kosch <[email protected]> and contributors"]
version = "0.2.0"
version = "0.2.1"

[deps]
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ makedocs(;
"tutorials/client_first_steps.md",
"tutorials/combined_variables.md",
"tutorials/combined_username_password_login.md",
"tutorials/combined_encrypted_un_pw_login.md",
"tutorials/further_resources.md"
],
"Manual" => [
Expand Down
119 changes: 119 additions & 0 deletions docs/src/tutorials/combined_encrypted_un_pw_login.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Encrypted username/password authentication using basic access control

In this tutorial, we will showcase how authentication using a username and password can be
accomplished using Open62541.jl. Following up from [Username/password authentication using basic access control](@ref)
the server and client will now be configured to use encryption, so that usernames and
passwords are transmitted safely across the network.

## Configuring the server
Here we configure the server to accept a username/password combination. We will also set up
encryption and disallow anonymous logins. The code block is commented line by line.

```julia
using Open62541

#generate a basic server certificate
#generate a basic server certificate
certificate = UA_ByteString_new()
privateKey = UA_ByteString_new()
subject = UA_String_Array_new([UA_String_fromChars("C=DE"),
UA_String_fromChars("O=SampleOrganization"),
UA_String_fromChars("CN=Open62541Server@localhost")])
lenSubject = UA_UInt32(3)
subjectAltName = UA_String_Array_new([UA_String_fromChars("DNS:localhost"),
UA_String_fromChars("URI:urn:open62541.server.application")])
lenSubjectAltName = UA_UInt32(2)
kvm = UA_KeyValueMap_new()
expiresIn = UA_UInt16(14)
retval0 = UA_KeyValueMap_setScalar(kvm, JUA_QualifiedName(0, "expires-in-days"), Ref(expiresIn),
UA_TYPES_PTRS[UA_TYPES_UINT16])
retval1 = UA_CreateCertificate(UA_Log_Stdout_new(UA_LOGLEVEL_FATAL), subject.ptr, lenSubject,
subjectAltName.ptr, lenSubjectAltName, UA_CERTIFICATEFORMAT_DER, kvm, privateKey,
certificate)

#configure the open62541 server; we choose a default config on port 4840.
server = JUA_Server()
config = JUA_ServerConfig(server)
JUA_ServerConfig_setDefault(config)
JUA_ServerConfig_addSecurityPolicyBasic256Sha256(config, certificate,
privateKey)
JUA_ServerConfig_addAllEndpoints(config)
config.securityPolicyNoneDiscoveryOnly = true
login = JUA_UsernamePasswordLogin("BruceWayne", "IamBatman") #specifies the user BruceWayne and his secret password.
allowAnonymous = false #disallow anonymous login
JUA_AccessControl_default(config, allowAnonymous, login) #set the access control inside the server config.

JUA_Server_runUntilInterrupt(server) #start the server, shut it down by pressing CTRL+C repeatedly once you are finished with it.
```

## Using the client
Start a new Julia session and run the program shown below. Once you are finished,
you may want to return to the first Julia session and stop the server (press
CTRL + C repeatedly). Again, the code block is commented line by line.

```julia
using Open62541

#initiate client, configure it and connect to server
client = JUA_Client()
config = UA_Client_getConfig(client)

#generate a client certificate
certificate = UA_ByteString_new()
privateKey = UA_ByteString_new()
subject = UA_String_Array_new([UA_String_fromChars("C=DE"),
UA_String_fromChars("O=SampleOrganization"),
UA_String_fromChars("CN=Open62541Client@localhost")])
lenSubject = UA_UInt32(3)
subjectAltName = UA_String_Array_new([UA_String_fromChars("DNS:localhost"),
UA_String_fromChars("URI:urn:open62541.client.application")])
lenSubjectAltName = UA_UInt32(2)
kvm = UA_KeyValueMap_new()
expiresIn = UA_UInt16(14)
UA_KeyValueMap_setScalar(kvm, JUA_QualifiedName(0, "expires-in-days"), Ref(expiresIn), UA_TYPES_PTRS[UA_TYPES_UINT16])
UA_CreateCertificate(UA_Log_Stdout_new(UA_LOGLEVEL_FATAL), subject.ptr, lenSubject,
subjectAltName.ptr, lenSubjectAltName, UA_CERTIFICATEFORMAT_DER, kvm, privateKey,
certificate)
revocationList = UA_ByteString_new()
revocationListSize = 0
trustList = UA_ByteString_new()
trustListSize = 0

UA_ClientConfig_setDefaultEncryption(config, certificate, privateKey,
trustList, trustListSize, revocationList, revocationListSize)

#set a few values manually
UA_CertificateVerification_AcceptAll(config.certificateVerification) #accept any server certificate
config.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT
config.clientDescription.applicationUri = UA_String_fromChars("urn:open62541.client.application")

retval1 = JUA_Client_connectUsername(client,
"opc.tcp://localhost:4840",
"BruceWayne",
"IamBatman") #connect using the username and password
JUA_Client_disconnect(client) #disconnect

#now let us try to connect with the wrong login credentials.
retval2 = JUA_Client_connectUsername(client,
"opc.tcp://localhost:4840",
"PeterParker",
"IamSpiderman") #try connecting using a wrong username/password

#now let us try connecting as an anonymous user
retval3 = JUA_Client_connect(client, "opc.tcp://localhost:4840")

#now let us try connecting without encryption
client = JUA_Client()
JUA_ClientConfig_setDefault(JUA_ClientConfig(client))
retval4 = JUA_Client_connectUsername(client,
"opc.tcp://localhost:4840",
"BruceWayne",
"IamBatman") #try connecting using a wrong username/password
```
`retval1` should be `UA_STATUSCODE_GOOD` (= 0) indicating that authentication was sucessful,
whereas `retval2` and `retval3` should be `UA_STATUSCODE_BADUSERACCESSDENIED` (= 2149515264)
indicating that the second login and third login attempt were rejected (wrong user
credentials). The fourth login attempt returns `retval4`, which should be
`UA_STATUSCODE_BADIDENTITYTOKENREJECTED` (= 2149646336), because we tried using an
unencrypted connection to a server that demands an encrypted one. Therefore, the server has
rejected the identity token.
21 changes: 11 additions & 10 deletions docs/src/tutorials/combined_username_password_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
In this tutorial, we will showcase how authentication using a username and password
(rather than an anonymous user) can be accomplished using Open62541.jl.

!!! warning
Note that in this basic configuration the login credentials are transmitted unencrypted
over the network, which is obviously *not recommended* when network traffic is
potentially exposed to unwanted listeners.

## Configuring the server
Here we configure the server to accept a The
code block is commented line by line.
Here we configure the server to accept a username/password combination. We will also disallow
anonymous logins. The code block is commented line by line.

```julia
using Open62541
Expand All @@ -16,7 +21,7 @@ config = JUA_ServerConfig(server)
JUA_ServerConfig_setDefault(config)
login = JUA_UsernamePasswordLogin("BruceWayne", "IamBatman") #specifies the user BruceWayne and his secret password.
allowAnonymous = false #disallow anonymous login
retval = JUA_AccessControl_default(config, allowAnonymous, login) #set the access control inside the server config.
JUA_AccessControl_default(config, allowAnonymous, login) #set the access control inside the server config.

JUA_Server_runUntilInterrupt(server) #start the server, shut it down by pressing CTRL+C repeatedly once you are finished with it.
```
Expand All @@ -32,9 +37,10 @@ using Open62541
#initiate client, configure it and connect to server
client = JUA_Client()
config = JUA_ClientConfig(client)
config.allowNonePolicyPassword = true #allow logging in with username/password on un-encrypted connections.
JUA_ClientConfig_setDefault(config)

retval = JUA_Client_connectUsername(client,
retval1 = JUA_Client_connectUsername(client,
"opc.tcp://localhost:4840",
"BruceWayne",
"IamBatman") #connect using the username and password
Expand All @@ -47,12 +53,7 @@ retval2 = JUA_Client_connectUsername(client,
"IamSpiderman") #try connecting using a wrong username/password

JUA_Client_disconnect(client) #disconnect

```
`retval` should be `UA_STATUSCODE_GOOD` (= 0) indicating that authentication was sucessful,
`retval1` should be `UA_STATUSCODE_GOOD` (= 0) indicating that authentication was sucessful,
whereas `retval2` should be `UA_STATUSCODE_BADUSERACCESSDENIED` (= 2149515264) indicating
that the second login attempt was rejected.

Note that in this basic configuration the login credentials are transmitted unencrypted,
which is obviously not recommended when network traffic is potentially exposed to
unwanted listeners.
147 changes: 147 additions & 0 deletions test/client_encryption.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using Distributed
Distributed.addprocs(1) # Add a single worker process to run the server

Distributed.@everywhere begin
using Open62541
using Test
using Pkg
end

# Create a new server running at a worker process
Distributed.@spawnat Distributed.workers()[end] begin
#generate a basic server certificate
certificate = UA_ByteString_new()
privateKey = UA_ByteString_new()
subject = UA_String_Array_new([UA_String_fromChars("C=DE"),
UA_String_fromChars("O=SampleOrganization"),
UA_String_fromChars("CN=Open62541Server@localhost")])
lenSubject = UA_UInt32(3)
subjectAltName = UA_String_Array_new([UA_String_fromChars("DNS:localhost"),
UA_String_fromChars("URI:urn:open62541.server.application")])
lenSubjectAltName = UA_UInt32(2)
kvm = UA_KeyValueMap_new()
expiresIn = UA_UInt16(14)
retval0 = UA_KeyValueMap_setScalar(kvm, JUA_QualifiedName(0, "expires-in-days"), Ref(expiresIn), UA_TYPES_PTRS[UA_TYPES_UINT16])
retval1 = UA_CreateCertificate(
UA_Log_Stdout_new(UA_LOGLEVEL_FATAL), subject.ptr, lenSubject, subjectAltName.ptr, lenSubjectAltName,
UA_CERTIFICATEFORMAT_DER, kvm, privateKey, certificate)

#configure server
server = JUA_Server()
config = JUA_ServerConfig(server)
retval2 = JUA_ServerConfig_setDefault(config)
retval3 = JUA_ServerConfig_addSecurityPolicyBasic256Sha256(config, certificate,
privateKey)
retval4 = JUA_ServerConfig_addAllEndpoints(config)
config.securityPolicyNoneDiscoveryOnly = true

#check
@test retval0 == UA_STATUSCODE_GOOD
@test retval1 == UA_STATUSCODE_GOOD
@test retval2 == UA_STATUSCODE_GOOD
@test retval3 == UA_STATUSCODE_GOOD
@test retval4 == UA_STATUSCODE_GOOD

#clean up
UA_KeyValueMap_delete(kvm)
UA_ByteString_delete(privateKey)
UA_ByteString_delete(certificate)

#run it
JUA_Server_runUntilInterrupt(server)
end

#client code
client = JUA_Client()
config = UA_Client_getConfig(client)

#generate a client certificate
certificate = UA_ByteString_new()
privateKey = UA_ByteString_new()
subject = UA_String_Array_new([UA_String_fromChars("C=DE"),
UA_String_fromChars("O=SampleOrganization"),
UA_String_fromChars("CN=Open62541Client@localhost")])
lenSubject = UA_UInt32(3)
subjectAltName = UA_String_Array_new([UA_String_fromChars("DNS:localhost"),
UA_String_fromChars("URI:urn:open62541.client.application")])
lenSubjectAltName = UA_UInt32(2)
kvm = UA_KeyValueMap_new()
expiresIn = UA_UInt16(14)
retval0 = UA_KeyValueMap_setScalar(kvm, JUA_QualifiedName(0, "expires-in-days"), Ref(expiresIn), UA_TYPES_PTRS[UA_TYPES_UINT16])
retval1 = UA_CreateCertificate(
UA_Log_Stdout_new(UA_LOGLEVEL_FATAL), subject.ptr, lenSubject, subjectAltName.ptr, lenSubjectAltName,
UA_CERTIFICATEFORMAT_DER, kvm, privateKey, certificate)
revocationList = UA_ByteString_new()
revocationListSize = 0
trustList = UA_ByteString_new()
trustListSize = 0

retval2 = UA_ClientConfig_setDefaultEncryption(config, certificate, privateKey,
trustList, trustListSize,
revocationList, revocationListSize)

#clean up
UA_ByteString_delete(revocationList)
UA_ByteString_delete(trustList)
UA_ByteString_delete(privateKey)
UA_ByteString_delete(certificate)
UA_KeyValueMap_delete(kvm)

#set a few values manually
UA_CertificateVerification_AcceptAll(config.certificateVerification) #accept any server certificate
config.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT
config.clientDescription.applicationUri = UA_String_fromChars("urn:open62541.client.application")

#check
@test retval0 == UA_STATUSCODE_GOOD
@test retval1 == UA_STATUSCODE_GOOD
@test retval2 == UA_STATUSCODE_GOOD

#now connect it
max_duration = 90.0 # Maximum waiting time for server startup
sleep_time = 2.0 # Sleep time in seconds between each connection trial
let trial
trial = 0
while trial < max_duration / sleep_time
retval = JUA_Client_connect(client, "opc.tcp://localhost:4840")
if retval == UA_STATUSCODE_GOOD
println("Connection established.")
break
end
sleep(sleep_time)
trial = trial + 1
end
@test trial < max_duration / sleep_time # Check if maximum number of trials has been exceeded
end

# Read nodeid from server
nodeid = JUA_NodeId(0, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_SOFTWAREVERSION)
open62541_version_server = JUA_Client_readValueAttribute(client, nodeid)

vn2string(vn::VersionNumber) = "$(vn.major).$(vn.minor).$(vn.patch)"
@static if VERSION < v"1.9"
pkgdir_old(m::Core.Module) = abspath(Base.pathof(Base.moduleroot(m)), "..", "..")
function pkgproject_old(m::Core.Module)
Pkg.Operations.read_project(Pkg.Types.projectfile_path(pkgdir_old(m)))
end
pkgversion_old(m::Core.Module) = pkgproject_old(m).version
open62541_version_julia = pkgversion_old(open62541_jll)
else
open62541_version_julia = pkgversion(open62541_jll)
end
open62541_version_julia = vn2string(open62541_version_julia)
@test open62541_version_server == open62541_version_julia

JUA_Client_disconnect(client)

#now try connecting unencrypted
client = JUA_Client()
retval4 = JUA_ClientConfig_setDefault(JUA_ClientConfig(client))
retval5 = JUA_Client_connect(client, "opc.tcp://localhost:4840")

@test retval4 == UA_STATUSCODE_GOOD
@test retval5 == UA_STATUSCODE_BADSECURITYPOLICYREJECTED

println("Ungracefully kill server process...")
Distributed.interrupt(Distributed.workers()[end])
Distributed.rmprocs(Distributed.workers()[end]; waitfor = 0)
4 changes: 4 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,7 @@ end
@testset "Username/password login & access control" begin
include("username_password_login_accesscontrol.jl")
end

@testset "Encryption" begin
include("client_encryption.jl")
end
2 changes: 1 addition & 1 deletion test/simple_server_client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ end
# Specify client and connect to server after server startup
client = JUA_Client()
JUA_ClientConfig_setDefault(JUA_ClientConfig(client))
max_duration = 30.0 # Maximum waiting time for server startup
max_duration = 90.0 # Maximum waiting time for server startup
sleep_time = 2.0 # Sleep time in seconds between each connection trial
let trial
trial = 0
Expand Down
1 change: 1 addition & 0 deletions test/username_password_login_accesscontrol.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Distributed.@spawnat Distributed.workers()[end] begin
config.accessControl.allowAddReference = cb_allowAddReference
config.accessControl.allowDeleteNode = cb_allowDeleteNode
config.accessControl.allowDeleteReference = cb_allowDeleteReference
config.allowNonePolicyPassword = true #allow logging in with username/password on un-encrypted connections.
UA_Server_run(server, Ref(true))
end

Expand Down