Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
opcua-asyncio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Nikola Balog
opcua-asyncio
Commits
be43b413
Commit
be43b413
authored
Dec 20, 2015
by
ORD
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #81 from alkor/security
Security policies implementation
parents
a80a7be6
a2d79771
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
434 additions
and
92 deletions
+434
-92
opcua/binary_client.py
opcua/binary_client.py
+17
-66
opcua/client.py
opcua/client.py
+16
-11
opcua/security_policies.py
opcua/security_policies.py
+334
-0
opcua/tools.py
opcua/tools.py
+32
-7
opcua/uaprotocol_hand.py
opcua/uaprotocol_hand.py
+32
-5
opcua/utils.py
opcua/utils.py
+3
-3
No files found.
opcua/binary_client.py
View file @
be43b413
...
@@ -10,15 +10,6 @@ from concurrent.futures import Future
...
@@ -10,15 +10,6 @@ from concurrent.futures import Future
import
opcua.uaprotocol
as
ua
import
opcua.uaprotocol
as
ua
import
opcua.utils
as
utils
import
opcua.utils
as
utils
class
CachedRequest
(
object
):
def
__init__
(
self
,
binary
):
self
.
binary
=
binary
def
to_binary
(
self
):
return
self
.
binary
class
UASocketClient
(
object
):
class
UASocketClient
(
object
):
"""
"""
handle socket connection and send ua messages
handle socket connection and send ua messages
...
@@ -111,62 +102,21 @@ class UASocketClient(object):
...
@@ -111,62 +102,21 @@ class UASocketClient(object):
break
break
self
.
logger
.
info
(
"Thread ended"
)
self
.
logger
.
info
(
"Thread ended"
)
def
_receive_header
(
self
):
self
.
logger
.
debug
(
"Waiting for header"
)
header
=
ua
.
Header
.
from_string
(
self
.
_socket
)
self
.
logger
.
info
(
"received header: %s"
,
header
)
return
header
def
_receive_body
(
self
,
size
):
self
.
logger
.
debug
(
"reading body of message (%s bytes)"
,
size
)
data
=
self
.
_socket
.
read
(
size
)
if
size
!=
len
(
data
):
raise
Exception
(
"Error, did not receive expected number of bytes, got {}, asked for {}"
.
format
(
len
(
data
),
size
))
return
utils
.
Buffer
(
data
)
def
_receive
(
self
):
def
_receive
(
self
):
body_chunk
=
b""
msg
=
ua
.
tcp_message_from_socket
(
self
.
_security_policy
,
self
.
_socket
)
while
True
:
if
isinstance
(
msg
,
ua
.
MessageChunk
):
ret
=
self
.
_receive_complete_msg
()
chunks
=
[
msg
]
if
ret
is
None
:
# TODO: check everything
return
while
chunks
[
-
1
].
MessageHeader
.
ChunkType
==
ua
.
ChunkType
.
Intermediate
:
hdr
,
algohdr
,
seqhdr
,
body
=
ret
chunks
.
append
(
ua
.
tcp_message_from_socket
(
self
.
_security_policy
,
self
.
_socket
))
if
hdr
.
ChunkType
in
(
b"F"
,
b"A"
):
body
=
b""
.
join
([
c
.
Body
for
c
in
chunks
])
body
.
data
=
body_chunk
+
body
.
data
self
.
_call_callback
(
msg
.
SequenceHeader
.
RequestId
,
utils
.
Buffer
(
body
))
break
elif
isinstance
(
msg
,
ua
.
Acknowledge
):
elif
hdr
.
ChunkType
==
b"C"
:
self
.
_call_callback
(
0
,
msg
)
self
.
logger
.
debug
(
"Received an intermediate message with header %s, waiting for next message"
,
hdr
)
elif
isinstance
(
msg
,
ua
.
ErrorMessage
):
body_chunk
+=
body
.
data
self
.
logger
.
warning
(
"Received an error: {}"
.
format
(
msg
))
else
:
self
.
logger
.
warning
(
"Received a message with unknown ChunkType %s, in header %s"
,
hdr
.
ChunkType
,
hdr
)
return
self
.
_call_callback
(
seqhdr
.
RequestId
,
body
)
def
_receive_complete_msg
(
self
):
header
=
self
.
_receive_header
()
if
header
is
None
:
return
None
body
=
self
.
_receive_body
(
header
.
body_size
)
if
header
.
MessageType
==
ua
.
MessageType
.
Error
:
self
.
logger
.
warning
(
"Received an error message type"
)
err
=
ua
.
ErrorMessage
.
from_binary
(
body
)
self
.
logger
.
warning
(
err
)
return
None
if
header
.
MessageType
==
ua
.
MessageType
.
Acknowledge
:
self
.
_call_callback
(
0
,
body
)
return
None
elif
header
.
MessageType
==
ua
.
MessageType
.
SecureOpen
:
algohdr
=
ua
.
AsymmetricAlgorithmHeader
.
from_binary
(
body
)
self
.
logger
.
info
(
algohdr
)
elif
header
.
MessageType
==
ua
.
MessageType
.
SecureMessage
:
algohdr
=
ua
.
SymmetricAlgorithmHeader
.
from_binary
(
body
)
self
.
logger
.
info
(
algohdr
)
else
:
else
:
self
.
logger
.
warning
(
"Unsupported message type: %s"
,
header
.
MessageType
)
raise
Exception
(
"Unsupported message type: {}"
.
format
(
msg
))
return
None
seqhdr
=
ua
.
SequenceHeader
.
from_binary
(
body
)
self
.
logger
.
debug
(
seqhdr
)
return
header
,
algohdr
,
seqhdr
,
body
def
_call_callback
(
self
,
request_id
,
body
):
def
_call_callback
(
self
,
request_id
,
body
):
with
self
.
_lock
:
with
self
.
_lock
:
...
@@ -219,7 +169,7 @@ class UASocketClient(object):
...
@@ -219,7 +169,7 @@ class UASocketClient(object):
with
self
.
_lock
:
with
self
.
_lock
:
self
.
_callbackmap
[
0
]
=
future
self
.
_callbackmap
[
0
]
=
future
self
.
_write_socket
(
header
,
hello
)
self
.
_write_socket
(
header
,
hello
)
ack
=
ua
.
Acknowledge
.
from_binary
(
future
.
result
(
self
.
timeout
)
)
ack
=
future
.
result
(
self
.
timeout
)
self
.
_max_chunk_size
=
ack
.
SendBufferSize
# client shouldn't send chunks larger than this
self
.
_max_chunk_size
=
ack
.
SendBufferSize
# client shouldn't send chunks larger than this
return
ack
return
ack
...
@@ -262,18 +212,19 @@ class BinaryClient(object):
...
@@ -262,18 +212,19 @@ class BinaryClient(object):
uaprotocol_auto.py and uaprotocol_hand.py
uaprotocol_auto.py and uaprotocol_hand.py
"""
"""
def
__init__
(
self
,
timeout
=
1
):
def
__init__
(
self
,
timeout
=
1
,
security_policy
=
ua
.
SecurityPolicy
()
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
_publishcallbacks
=
{}
self
.
_publishcallbacks
=
{}
self
.
_lock
=
Lock
()
self
.
_lock
=
Lock
()
self
.
_timeout
=
timeout
self
.
_timeout
=
timeout
self
.
_uasocket
=
None
self
.
_uasocket
=
None
self
.
_security_policy
=
security_policy
def
connect_socket
(
self
,
host
,
port
):
def
connect_socket
(
self
,
host
,
port
):
"""
"""
connect to server socket and start receiving thread
connect to server socket and start receiving thread
"""
"""
self
.
_uasocket
=
UASocketClient
(
self
.
_timeout
)
self
.
_uasocket
=
UASocketClient
(
self
.
_timeout
,
security_policy
=
self
.
_security_policy
)
return
self
.
_uasocket
.
connect_socket
(
host
,
port
)
return
self
.
_uasocket
.
connect_socket
(
host
,
port
)
def
disconnect_socket
(
self
):
def
disconnect_socket
(
self
):
...
...
opcua/client.py
View file @
be43b413
...
@@ -67,7 +67,7 @@ class Client(object):
...
@@ -67,7 +67,7 @@ class Client(object):
which offers a raw OPC-UA interface.
which offers a raw OPC-UA interface.
"""
"""
def
__init__
(
self
,
url
,
timeout
=
1
):
def
__init__
(
self
,
url
,
timeout
=
1
,
security_policy
=
ua
.
SecurityPolicy
()
):
"""
"""
used url argument to connect to server.
used url argument to connect to server.
if you are unsure of url, write at least hostname and port
if you are unsure of url, write at least hostname and port
...
@@ -82,8 +82,7 @@ class Client(object):
...
@@ -82,8 +82,7 @@ class Client(object):
self
.
description
=
self
.
name
self
.
description
=
self
.
name
self
.
application_uri
=
"urn:freeopcua:client"
self
.
application_uri
=
"urn:freeopcua:client"
self
.
product_uri
=
"urn:freeopcua.github.no:client"
self
.
product_uri
=
"urn:freeopcua.github.no:client"
self
.
security_policy_uri
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
self
.
security_policy
=
security_policy
self
.
security_mode
=
ua
.
MessageSecurityMode
.
None_
self
.
secure_channel_id
=
None
self
.
secure_channel_id
=
None
self
.
default_timeout
=
3600000
self
.
default_timeout
=
3600000
self
.
secure_channel_timeout
=
self
.
default_timeout
self
.
secure_channel_timeout
=
self
.
default_timeout
...
@@ -92,8 +91,7 @@ class Client(object):
...
@@ -92,8 +91,7 @@ class Client(object):
self
.
server_certificate
=
b""
self
.
server_certificate
=
b""
self
.
client_certificate
=
b""
self
.
client_certificate
=
b""
self
.
private_key
=
b""
self
.
private_key
=
b""
self
.
bclient
=
BinaryClient
(
timeout
)
self
.
bclient
=
BinaryClient
(
timeout
,
security_policy
=
security_policy
)
self
.
_nonce
=
None
self
.
_session_counter
=
1
self
.
_session_counter
=
1
self
.
keepalive
=
None
self
.
keepalive
=
None
...
@@ -192,10 +190,12 @@ class Client(object):
...
@@ -192,10 +190,12 @@ class Client(object):
params
.
RequestType
=
ua
.
SecurityTokenRequestType
.
Issue
params
.
RequestType
=
ua
.
SecurityTokenRequestType
.
Issue
if
renew
:
if
renew
:
params
.
RequestType
=
ua
.
SecurityTokenRequestType
.
Renew
params
.
RequestType
=
ua
.
SecurityTokenRequestType
.
Renew
params
.
SecurityMode
=
self
.
security_
m
ode
params
.
SecurityMode
=
self
.
security_
policy
.
M
ode
params
.
RequestedLifetime
=
self
.
secure_channel_timeout
params
.
RequestedLifetime
=
self
.
secure_channel_timeout
params
.
ClientNonce
=
'
\
x00
'
nonce
=
utils
.
create_nonce
(
self
.
security_policy
.
symmetric_key_size
)
# length should be equal to the length of key of symmetric encryption
params
.
ClientNonce
=
nonce
# this nonce is used to create a symmetric key
result
=
self
.
bclient
.
open_secure_channel
(
params
)
result
=
self
.
bclient
.
open_secure_channel
(
params
)
self
.
security_policy
.
make_symmetric_key
(
nonce
,
result
.
ServerNonce
)
self
.
secure_channel_timeout
=
result
.
SecurityToken
.
RevisedLifetime
self
.
secure_channel_timeout
=
result
.
SecurityToken
.
RevisedLifetime
def
close_secure_channel
(
self
):
def
close_secure_channel
(
self
):
...
@@ -251,18 +251,20 @@ class Client(object):
...
@@ -251,18 +251,20 @@ class Client(object):
desc
.
ApplicationType
=
ua
.
ApplicationType
.
Client
desc
.
ApplicationType
=
ua
.
ApplicationType
.
Client
params
=
ua
.
CreateSessionParameters
()
params
=
ua
.
CreateSessionParameters
()
params
.
ClientNonce
=
utils
.
create_nonce
()
nonce
=
utils
.
create_nonce
(
32
)
# at least 32 random bytes for server to prove possession of private key (specs part 4, 5.6.2.2)
params
.
ClientCertificate
=
b''
params
.
ClientNonce
=
nonce
params
.
ClientCertificate
=
self
.
security_policy
.
client_certificate
params
.
ClientDescription
=
desc
params
.
ClientDescription
=
desc
params
.
EndpointUrl
=
self
.
server_url
.
geturl
()
params
.
EndpointUrl
=
self
.
server_url
.
geturl
()
params
.
SessionName
=
self
.
description
+
" Session"
+
str
(
self
.
_session_counter
)
params
.
SessionName
=
self
.
description
+
" Session"
+
str
(
self
.
_session_counter
)
params
.
RequestedSessionTimeout
=
3600000
params
.
RequestedSessionTimeout
=
3600000
params
.
MaxResponseMessageSize
=
0
# means no max size
params
.
MaxResponseMessageSize
=
0
# means no max size
params
.
ClientCertificate
=
self
.
client_certificate
response
=
self
.
bclient
.
create_session
(
params
)
response
=
self
.
bclient
.
create_session
(
params
)
self
.
security_policy
.
asymmetric_cryptography
.
verify
(
self
.
security_policy
.
client_certificate
+
nonce
,
response
.
ServerSignature
.
Signature
)
self
.
_server_nonce
=
response
.
ServerNonce
self
.
server_certificate
=
response
.
ServerCertificate
self
.
server_certificate
=
response
.
ServerCertificate
for
ep
in
response
.
ServerEndpoints
:
for
ep
in
response
.
ServerEndpoints
:
if
urlparse
(
ep
.
EndpointUrl
).
scheme
==
self
.
server_url
.
scheme
and
ep
.
SecurityMode
==
self
.
security_
m
ode
:
if
urlparse
(
ep
.
EndpointUrl
).
scheme
==
self
.
server_url
.
scheme
and
ep
.
SecurityMode
==
self
.
security_
policy
.
M
ode
:
# remember PolicyId's: we will use them in activate_session()
# remember PolicyId's: we will use them in activate_session()
self
.
_policy_ids
=
ep
.
UserIdentityTokens
self
.
_policy_ids
=
ep
.
UserIdentityTokens
self
.
session_timeout
=
response
.
RevisedSessionTimeout
self
.
session_timeout
=
response
.
RevisedSessionTimeout
...
@@ -285,6 +287,9 @@ class Client(object):
...
@@ -285,6 +287,9 @@ class Client(object):
Activate session using either username and password or private_key
Activate session using either username and password or private_key
"""
"""
params
=
ua
.
ActivateSessionParameters
()
params
=
ua
.
ActivateSessionParameters
()
challenge
=
self
.
security_policy
.
server_certificate
+
self
.
_server_nonce
params
.
ClientSignature
.
Algorithm
=
b"http://www.w3.org/2000/09/xmldsig#rsa-sha1"
params
.
ClientSignature
.
Signature
=
self
.
security_policy
.
asymmetric_cryptography
.
signature
(
challenge
)
params
.
LocaleIds
.
append
(
"en"
)
params
.
LocaleIds
.
append
(
"en"
)
if
not
username
and
not
certificate
:
if
not
username
and
not
certificate
:
params
.
UserIdentityToken
=
ua
.
AnonymousIdentityToken
()
params
.
UserIdentityToken
=
ua
.
AnonymousIdentityToken
()
...
...
opcua/security_policies.py
0 → 100644
View file @
be43b413
from
opcua.uaprotocol
import
SecurityPolicy
,
MessageSecurityMode
,
CryptographyNone
try
:
from
cryptography
import
x509
,
exceptions
from
cryptography.hazmat.backends
import
default_backend
from
cryptography.hazmat.primitives.asymmetric
import
padding
from
cryptography.hazmat.primitives.ciphers
import
Cipher
,
algorithms
,
modes
from
cryptography.hazmat.primitives
import
hashes
,
serialization
,
hmac
cryptography_available
=
True
except
ImportError
:
cryptography_available
=
False
def
require_cryptography
(
obj
):
if
not
cryptography_available
:
raise
Exception
(
"Can't use {}, cryptography module is not installed"
.
format
(
obj
.
__class__
.
__name__
))
class
Cryptography
(
CryptographyNone
):
"""
Security policy: Sign or SignAndEncrypt
"""
def
__init__
(
self
,
mode
=
MessageSecurityMode
.
Sign
):
self
.
Signer
=
None
self
.
Verifier
=
None
self
.
Encryptor
=
None
self
.
Decryptor
=
None
assert
(
mode
in
(
MessageSecurityMode
.
Sign
,
MessageSecurityMode
.
SignAndEncrypt
))
self
.
is_encrypted
=
(
mode
==
MessageSecurityMode
.
SignAndEncrypt
)
def
plain_block_size
(
self
):
"""
Size of plain text block for block cipher.
"""
if
self
.
is_encrypted
:
return
self
.
Encryptor
.
plain_block_size
()
return
1
def
encrypted_block_size
(
self
):
"""
Size of encrypted text block for block cipher.
"""
if
self
.
is_encrypted
:
return
self
.
Encryptor
.
encrypted_block_size
()
return
1
def
padding
(
self
,
size
):
"""
Create padding for a block of given size.
plain_size = size + len(padding) + signature_size()
plain_size = N * plain_block_size()
"""
if
not
self
.
is_encrypted
:
return
b''
rem
=
(
size
+
self
.
signature_size
()
+
1
)
%
self
.
Encryptor
.
plain_block_size
()
if
rem
!=
0
:
rem
=
self
.
Encryptor
.
plain_block_size
()
-
rem
return
bytes
(
bytearray
([
rem
]))
*
(
rem
+
1
)
def
min_padding_size
(
self
):
if
self
.
is_encrypted
:
return
1
return
0
def
signature_size
(
self
):
return
self
.
Signer
.
signature_size
()
def
signature
(
self
,
data
):
return
self
.
Signer
.
signature
(
data
)
def
vsignature_size
(
self
):
return
self
.
Verifier
.
signature_size
()
def
verify
(
self
,
data
,
sig
):
self
.
Verifier
.
verify
(
data
,
sig
)
def
encrypt
(
self
,
data
):
if
self
.
is_encrypted
:
assert
(
len
(
data
)
%
self
.
Encryptor
.
plain_block_size
()
==
0
)
return
self
.
Encryptor
.
encrypt
(
data
)
return
data
def
decrypt
(
self
,
data
):
if
self
.
is_encrypted
:
#assert(len(data) % self.Decryptor.encrypted_block_size() == 0)
return
self
.
Decryptor
.
decrypt
(
data
)
return
data
def
remove_padding
(
self
,
data
):
if
self
.
is_encrypted
:
pad_size
=
bytearray
(
data
[
-
1
:])[
0
]
+
1
return
data
[:
-
pad_size
]
return
data
class
SignerRsa
:
def
__init__
(
self
,
client_pk
):
require_cryptography
(
self
)
self
.
client_pk
=
serialization
.
load_pem_private_key
(
client_pk
,
None
,
default_backend
())
self
.
key_size
=
self
.
client_pk
.
key_size
//
8
def
signature_size
(
self
):
assert
(
len
(
self
.
signature
(
b''
))
==
self
.
key_size
)
return
self
.
key_size
def
signature
(
self
,
data
):
s
=
self
.
client_pk
.
signer
(
padding
.
PKCS1v15
(),
hashes
.
SHA1
())
s
.
update
(
data
)
return
s
.
finalize
()
class
VerifierRsa
:
def
__init__
(
self
,
server_cert
):
require_cryptography
(
self
)
self
.
server_cert
=
x509
.
load_der_x509_certificate
(
server_cert
,
default_backend
())
self
.
key_size
=
self
.
server_cert
.
public_key
().
key_size
//
8
def
signature_size
(
self
):
return
self
.
key_size
def
verify
(
self
,
data
,
signature
):
verifier
=
self
.
server_cert
.
public_key
().
verifier
(
signature
,
padding
.
PKCS1v15
(),
hashes
.
SHA1
())
verifier
.
update
(
data
)
verifier
.
verify
()
class
EncryptorRsa
:
def
__init__
(
self
,
server_cert
,
padding
,
padding_size
):
require_cryptography
(
self
)
self
.
server_cert
=
x509
.
load_der_x509_certificate
(
server_cert
,
default_backend
())
self
.
key_size
=
self
.
server_cert
.
public_key
().
key_size
//
8
self
.
padding
=
padding
self
.
padding_size
=
padding_size
def
plain_block_size
(
self
):
return
self
.
key_size
-
self
.
padding_size
def
encrypted_block_size
(
self
):
return
self
.
key_size
def
encrypt
(
self
,
data
):
e
=
b''
for
i
in
range
(
0
,
len
(
data
),
self
.
plain_block_size
()):
e
+=
self
.
server_cert
.
public_key
().
encrypt
(
data
[
i
:
i
+
self
.
plain_block_size
()],
self
.
padding
)
return
e
class
DecryptorRsa
:
def
__init__
(
self
,
client_pk
,
padding
,
padding_size
):
require_cryptography
(
self
)
self
.
client_pk
=
serialization
.
load_pem_private_key
(
client_pk
,
None
,
default_backend
())
self
.
key_size
=
self
.
client_pk
.
key_size
//
8
self
.
padding
=
padding
self
.
padding_size
=
padding_size
def
plain_block_size
(
self
):
return
self
.
key_size
-
self
.
padding_size
def
encrypted_block_size
(
self
):
return
self
.
key_size
def
decrypt
(
self
,
data
):
d
=
b''
for
i
in
range
(
0
,
len
(
data
),
self
.
encrypted_block_size
()):
d
+=
self
.
client_pk
.
decrypt
(
data
[
i
:
i
+
self
.
encrypted_block_size
()],
self
.
padding
)
return
d
class
SignerAesCbc
:
def
__init__
(
self
,
key
):
require_cryptography
(
self
)
self
.
key
=
key
def
signature_size
(
self
):
return
hashes
.
SHA1
.
digest_size
def
signature
(
self
,
data
):
h
=
hmac
.
HMAC
(
self
.
key
,
hashes
.
SHA1
(),
backend
=
default_backend
())
h
.
update
(
data
)
return
h
.
finalize
()
class
VerifierAesCbc
:
def
__init__
(
self
,
key
):
require_cryptography
(
self
)
self
.
key
=
key
def
signature_size
(
self
):
return
hashes
.
SHA1
.
digest_size
def
verify
(
self
,
data
,
signature
):
h
=
hmac
.
HMAC
(
self
.
key
,
hashes
.
SHA1
(),
backend
=
default_backend
())
h
.
update
(
data
)
expected
=
h
.
finalize
()
if
signature
!=
expected
:
raise
exceptions
.
InvalidSignature
class
EncryptorAesCbc
:
def
__init__
(
self
,
key
,
iv
):
require_cryptography
(
self
)
self
.
cipher
=
Cipher
(
algorithms
.
AES
(
key
),
modes
.
CBC
(
iv
),
backend
=
default_backend
())
def
plain_block_size
(
self
):
return
self
.
cipher
.
algorithm
.
key_size
//
8
def
encrypted_block_size
(
self
):
return
self
.
cipher
.
algorithm
.
key_size
//
8
def
encrypt
(
self
,
data
):
encryptor
=
self
.
cipher
.
encryptor
()
return
encryptor
.
update
(
data
)
+
encryptor
.
finalize
()
class
DecryptorAesCbc
:
def
__init__
(
self
,
key
,
iv
):
require_cryptography
(
self
)
self
.
cipher
=
Cipher
(
algorithms
.
AES
(
key
),
modes
.
CBC
(
iv
),
backend
=
default_backend
())
def
plain_block_size
(
self
):
return
self
.
cipher
.
algorithm
.
key_size
//
8
def
encrypted_block_size
(
self
):
return
self
.
cipher
.
algorithm
.
key_size
//
8
def
decrypt
(
self
,
data
):
decryptor
=
self
.
cipher
.
decryptor
()
return
decryptor
.
update
(
data
)
+
decryptor
.
finalize
()
def
hash_hmac
(
a
,
b
):
h
=
hmac
.
HMAC
(
a
,
hashes
.
SHA1
(),
backend
=
default_backend
())
h
.
update
(
b
)
return
h
.
finalize
()
def
p_sha1
(
key
,
body
,
sizes
=
()):
size
=
0
for
s
in
sizes
:
size
+=
s
result
=
b''
accum
=
body
while
len
(
result
)
<
size
:
accum
=
hash_hmac
(
key
,
accum
)
result
+=
hash_hmac
(
key
,
accum
+
body
)
parts
=
[]
for
s
in
sizes
:
parts
.
append
(
result
[:
s
])
result
=
result
[
s
:]
return
tuple
(
parts
)
class
SecurityPolicyBasic128Rsa15
(
SecurityPolicy
):
"""
Security Basic 128Rsa15
A suite of algorithms that uses RSA15 as Key-Wrap-algorithm and 128-Bit for encryption algorithms.
-> SymmetricSignatureAlgorithm – HmacSha1 – (http://www.w3.org/2000/09/xmldsig#hmac-sha1).
-> SymmetricEncryptionAlgorithm – Aes128 – (http://www.w3.org/2001/04/xmlenc#aes128-cbc).
-> AsymmetricSignatureAlgorithm – RsaSha1 – (http://www.w3.org/2000/09/xmldsig#rsa-sha1).
-> AsymmetricKeyWrapAlgorithm – KwRsa15 – (http://www.w3.org/2001/04/xmlenc#rsa-1_5).
-> AsymmetricEncryptionAlgorithm – Rsa15 – (http://www.w3.org/2001/04/xmlenc#rsa-1_5).
-> KeyDerivationAlgorithm – PSha1 – (http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1).
-> DerivedSignatureKeyLength – 128 (16 bytes)
-> MinAsymmetricKeyLength – 1024 (128 bytes)
-> MaxAsymmetricKeyLength – 2048 (256 bytes)
-> CertificateSignatureAlgorithm – Sha1
If a certificate or any certificate in the chain is not signed with a hash that is Sha1 or stronger then the certificate shall be rejected.
"""
URI
=
"http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"
signature_key_size
=
16
symmetric_key_size
=
16
def
__init__
(
self
,
server_cert
,
client_cert
,
client_pk
,
mode
=
MessageSecurityMode
.
SignAndEncrypt
):
require_cryptography
(
self
)
self
.
asymmetric_cryptography
=
Cryptography
(
MessageSecurityMode
.
SignAndEncrypt
)
self
.
asymmetric_cryptography
.
Signer
=
SignerRsa
(
client_pk
)
self
.
asymmetric_cryptography
.
Verifier
=
VerifierRsa
(
server_cert
)
self
.
asymmetric_cryptography
.
Encryptor
=
EncryptorRsa
(
server_cert
,
padding
.
PKCS1v15
(),
11
)
self
.
asymmetric_cryptography
.
Decryptor
=
DecryptorRsa
(
client_pk
,
padding
.
PKCS1v15
(),
11
)
self
.
symmetric_cryptography
=
Cryptography
(
mode
)
self
.
Mode
=
mode
self
.
server_certificate
=
server_cert
self
.
client_certificate
=
client_cert
def
make_symmetric_key
(
self
,
n1
,
n2
):
(
sigkey
,
key
,
iv
)
=
p_sha1
(
n2
,
n1
,
(
self
.
signature_key_size
,
self
.
symmetric_key_size
,
16
))
self
.
symmetric_cryptography
.
Signer
=
SignerAesCbc
(
sigkey
)
self
.
symmetric_cryptography
.
Encryptor
=
EncryptorAesCbc
(
key
,
iv
)
(
sigkey2
,
key2
,
iv2
)
=
p_sha1
(
n1
,
n2
,
(
self
.
signature_key_size
,
self
.
symmetric_key_size
,
16
))
self
.
symmetric_cryptography
.
Verifier
=
VerifierAesCbc
(
sigkey2
)
self
.
symmetric_cryptography
.
Decryptor
=
DecryptorAesCbc
(
key2
,
iv2
)
def
oaep
():
return
padding
.
OAEP
(
padding
.
MGF1
(
hashes
.
SHA1
()),
hashes
.
SHA1
(),
None
)
class
SecurityPolicyBasic256
(
SecurityPolicy
):
"""
Security Basic 256
A suite of algorithms that are for 256-Bit encryption, algorithms include:
-> SymmetricSignatureAlgorithm – HmacSha1 – (http://www.w3.org/2000/09/xmldsig#hmac-sha1).
-> SymmetricEncryptionAlgorithm – Aes256 – (http://www.w3.org/2001/04/xmlenc#aes256-cbc).
-> AsymmetricSignatureAlgorithm – RsaSha1 – (http://www.w3.org/2000/09/xmldsig#rsa-sha1).
-> AsymmetricKeyWrapAlgorithm – KwRsaOaep – (http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p).
-> AsymmetricEncryptionAlgorithm – RsaOaep – (http://www.w3.org/2001/04/xmlenc#rsa-oaep).
-> KeyDerivationAlgorithm – PSha1 – (http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1).
-> DerivedSignatureKeyLength – 192 (24 bytes)
-> MinAsymmetricKeyLength – 1024 (128 bytes)
-> MaxAsymmetricKeyLength – 2048 (256 bytes)
-> CertificateSignatureAlgorithm – Sha1
If a certificate or any certificate in the chain is not signed with a hash that is Sha1 or stronger then the certificate shall be rejected.
"""
URI
=
"http://opcfoundation.org/UA/SecurityPolicy#Basic256"
signature_key_size
=
24
symmetric_key_size
=
32
def
__init__
(
self
,
server_cert
,
client_cert
,
client_pk
,
mode
=
MessageSecurityMode
.
SignAndEncrypt
):
require_cryptography
(
self
)
self
.
asymmetric_cryptography
=
Cryptography
(
MessageSecurityMode
.
SignAndEncrypt
)
self
.
asymmetric_cryptography
.
Signer
=
SignerRsa
(
client_pk
)
self
.
asymmetric_cryptography
.
Verifier
=
VerifierRsa
(
server_cert
)
self
.
asymmetric_cryptography
.
Encryptor
=
EncryptorRsa
(
server_cert
,
oaep
(),
42
)
self
.
asymmetric_cryptography
.
Decryptor
=
DecryptorRsa
(
client_pk
,
oaep
(),
42
)
self
.
symmetric_cryptography
=
Cryptography
(
mode
)
self
.
Mode
=
mode
self
.
server_certificate
=
server_cert
self
.
client_certificate
=
client_cert
def
make_symmetric_key
(
self
,
n1
,
n2
):
(
sigkey
,
key
,
iv
)
=
p_sha1
(
n2
,
n1
,
(
self
.
signature_key_size
,
self
.
symmetric_key_size
,
16
))
self
.
symmetric_cryptography
.
Signer
=
SignerAesCbc
(
sigkey
)
self
.
symmetric_cryptography
.
Encryptor
=
EncryptorAesCbc
(
key
,
iv
)
(
sigkey2
,
key2
,
iv2
)
=
p_sha1
(
n1
,
n2
,
(
self
.
signature_key_size
,
self
.
symmetric_key_size
,
16
))
self
.
symmetric_cryptography
.
Verifier
=
VerifierAesCbc
(
sigkey2
)
self
.
symmetric_cryptography
.
Decryptor
=
DecryptorAesCbc
(
key2
,
iv2
)
opcua/tools.py
View file @
be43b413
...
@@ -19,6 +19,7 @@ from opcua import Client
...
@@ -19,6 +19,7 @@ from opcua import Client
from
opcua
import
Server
from
opcua
import
Server
from
opcua
import
Node
from
opcua
import
Node
from
opcua
import
uamethod
from
opcua
import
uamethod
from
opcua
import
security_policies
def
add_minimum_args
(
parser
):
def
add_minimum_args
(
parser
):
...
@@ -58,7 +59,29 @@ def add_common_args(parser):
...
@@ -58,7 +59,29 @@ def add_common_args(parser):
type
=
int
,
type
=
int
,
default
=
0
,
default
=
0
,
metavar
=
"NAMESPACE"
)
metavar
=
"NAMESPACE"
)
parser
.
add_argument
(
"--security"
,
help
=
"Security settings, for example: Basic256,SignAndEncrypt,cert.der,pk.pem[,server_cert.der]. Default: None"
,
default
=
''
)
def
client_security
(
security
,
url
,
timeout
):
parts
=
security
.
split
(
','
)
if
len
(
parts
)
<
4
:
raise
Exception
(
'Wrong format: `{}`, expected at least 4 comma-separated values'
.
format
(
security
))
policy_class
=
getattr
(
security_policies
,
'SecurityPolicy'
+
parts
[
0
])
mode
=
getattr
(
ua
.
MessageSecurityMode
,
parts
[
1
])
cert
=
open
(
parts
[
2
],
'rb'
).
read
()
pk
=
open
(
parts
[
3
],
'rb'
).
read
()
server_cert
=
None
if
len
(
parts
)
==
5
:
server_cert
=
open
(
parts
[
4
],
'rb'
).
read
()
else
:
# we need server's certificate too. Let's get it from the list of endpoints
client
=
Client
(
url
,
timeout
=
timeout
)
for
ep
in
client
.
connect_and_get_server_endpoints
():
if
ep
.
EndpointUrl
.
startswith
(
ua
.
OPC_TCP_SCHEME
)
and
ep
.
SecurityMode
==
mode
and
ep
.
SecurityPolicyUri
==
policy_class
.
URI
:
server_cert
=
ep
.
ServerCertificate
return
policy_class
(
server_cert
,
cert
,
pk
,
mode
)
def
parse_args
(
parser
,
requirenodeid
=
False
):
def
parse_args
(
parser
,
requirenodeid
=
False
):
args
=
parser
.
parse_args
()
args
=
parser
.
parse_args
()
...
@@ -66,6 +89,8 @@ def parse_args(parser, requirenodeid=False):
...
@@ -66,6 +89,8 @@ def parse_args(parser, requirenodeid=False):
if
args
.
url
and
'://'
not
in
args
.
url
:
if
args
.
url
and
'://'
not
in
args
.
url
:
logging
.
info
(
"Adding default scheme %s to URL %s"
,
ua
.
OPC_TCP_SCHEME
,
args
.
url
)
logging
.
info
(
"Adding default scheme %s to URL %s"
,
ua
.
OPC_TCP_SCHEME
,
args
.
url
)
args
.
url
=
ua
.
OPC_TCP_SCHEME
+
'://'
+
args
.
url
args
.
url
=
ua
.
OPC_TCP_SCHEME
+
'://'
+
args
.
url
if
hasattr
(
args
,
'security'
)
and
args
.
security
:
args
.
security
=
client_security
(
args
.
security
,
args
.
url
,
args
.
timeout
)
# check that a nodeid has been given explicitly, a bit hackish...
# check that a nodeid has been given explicitly, a bit hackish...
if
requirenodeid
and
args
.
nodeid
==
"i=84"
and
args
.
path
==
""
:
if
requirenodeid
and
args
.
nodeid
==
"i=84"
and
args
.
path
==
""
:
parser
.
print_usage
()
parser
.
print_usage
()
...
@@ -99,7 +124,7 @@ def uaread():
...
@@ -99,7 +124,7 @@ def uaread():
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
client
.
connect
()
try
:
try
:
node
=
get_node
(
client
,
args
)
node
=
get_node
(
client
,
args
)
...
@@ -235,7 +260,7 @@ def uawrite():
...
@@ -235,7 +260,7 @@ def uawrite():
metavar
=
"VALUE"
)
metavar
=
"VALUE"
)
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
client
.
connect
()
try
:
try
:
node
=
get_node
(
client
,
args
)
node
=
get_node
(
client
,
args
)
...
@@ -266,7 +291,7 @@ def uals():
...
@@ -266,7 +291,7 @@ def uals():
if
args
.
long_format
is
None
:
if
args
.
long_format
is
None
:
args
.
long_format
=
1
args
.
long_format
=
1
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
client
.
connect
()
try
:
try
:
node
=
get_node
(
client
,
args
)
node
=
get_node
(
client
,
args
)
...
@@ -351,7 +376,7 @@ def uasubscribe():
...
@@ -351,7 +376,7 @@ def uasubscribe():
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
client
.
connect
()
try
:
try
:
node
=
get_node
(
client
,
args
)
node
=
get_node
(
client
,
args
)
...
@@ -432,7 +457,7 @@ def uaclient():
...
@@ -432,7 +457,7 @@ def uaclient():
help
=
"set client private key"
)
help
=
"set client private key"
)
args
=
parse_args
(
parser
)
args
=
parse_args
(
parser
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
client
.
connect
()
if
args
.
certificate
:
if
args
.
certificate
:
client
.
load_client_certificate
(
args
.
certificate
)
client
.
load_client_certificate
(
args
.
certificate
)
...
@@ -593,7 +618,7 @@ def uahistoryread():
...
@@ -593,7 +618,7 @@ def uahistoryread():
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
args
=
parse_args
(
parser
,
requirenodeid
=
True
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
client
.
connect
()
try
:
try
:
node
=
get_node
(
client
,
args
)
node
=
get_node
(
client
,
args
)
...
...
opcua/uaprotocol_hand.py
View file @
be43b413
...
@@ -254,7 +254,7 @@ class CryptographyNone:
...
@@ -254,7 +254,7 @@ class CryptographyNone:
"""
"""
Base class for symmetric/asymmetric cryprography
Base class for symmetric/asymmetric cryprography
"""
"""
def
__init__
(
self
,
mode
=
auto
.
MessageSecurityMode
.
None_
):
def
__init__
(
self
):
pass
pass
def
plain_block_size
(
self
):
def
plain_block_size
(
self
):
...
@@ -309,17 +309,17 @@ class SecurityPolicy:
...
@@ -309,17 +309,17 @@ class SecurityPolicy:
"""
"""
Base class for security policy
Base class for security policy
"""
"""
URI
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
signature_key_size
=
0
symmetric_key_size
=
0
def
__init__
(
self
):
def
__init__
(
self
):
self
.
asymmetric_cryptography
=
CryptographyNone
()
self
.
asymmetric_cryptography
=
CryptographyNone
()
self
.
symmetric_cryptography
=
CryptographyNone
()
self
.
symmetric_cryptography
=
CryptographyNone
()
self
.
Mode
=
auto
.
MessageSecurityMode
.
None_
self
.
Mode
=
auto
.
MessageSecurityMode
.
None_
self
.
URI
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
self
.
server_certificate
=
b""
self
.
server_certificate
=
b""
self
.
client_certificate
=
b""
self
.
client_certificate
=
b""
def
symmetric_key_size
(
self
):
return
0
def
make_symmetric_key
(
self
,
a
,
b
):
def
make_symmetric_key
(
self
,
a
,
b
):
pass
pass
...
@@ -433,6 +433,33 @@ class MessageChunk(uatypes.FrozenClass):
...
@@ -433,6 +433,33 @@ class MessageChunk(uatypes.FrozenClass):
__repr__
=
__str__
__repr__
=
__str__
def
tcp_message_from_header_and_body
(
security_policy
,
header
,
body
):
"""
Convert binary stream to OPC UA TCP message (see OPC UA specs Part 6, 7.1)
The only supported message types are Hello, Acknowledge and Error
"""
if
header
.
MessageType
in
(
MessageType
.
SecureOpen
,
MessageType
.
SecureClose
,
MessageType
.
SecureMessage
):
return
MessageChunk
.
from_header_and_body
(
security_policy
,
header
,
body
)
elif
header
.
MessageType
==
MessageType
.
Hello
:
return
Hello
.
from_binary
(
body
)
elif
header
.
MessageType
==
MessageType
.
Acknowledge
:
return
Acknowledge
.
from_binary
(
body
)
elif
header
.
MessageType
==
MessageType
.
Error
:
return
ErrorMessage
.
from_binary
(
body
)
else
:
raise
Exception
(
"Unsupported message type {}"
.
format
(
header
.
MessageType
))
def
tcp_message_from_socket
(
security_policy
,
socket
):
logger
.
debug
(
"Waiting for header"
)
header
=
Header
.
from_string
(
socket
)
logger
.
info
(
"received header: %s"
,
header
)
body
=
socket
.
read
(
header
.
body_size
)
if
len
(
body
)
!=
header
.
body_size
:
raise
Exception
(
"{} bytes expected, {} available"
.
format
(
header
.
body_size
,
len
(
body
)))
return
tcp_message_from_header_and_body
(
security_policy
,
header
,
utils
.
Buffer
(
body
))
# FIXES for missing switchfield in NodeAttributes classes
# FIXES for missing switchfield in NodeAttributes classes
ana
=
auto
.
NodeAttributesMask
ana
=
auto
.
NodeAttributesMask
...
...
opcua/utils.py
View file @
be43b413
import
logging
import
logging
import
uuid
import
os
from
concurrent.futures
import
Future
from
concurrent.futures
import
Future
import
functools
import
functools
import
threading
import
threading
...
@@ -108,8 +108,8 @@ class SocketWrapper(object):
...
@@ -108,8 +108,8 @@ class SocketWrapper(object):
def
create_nonce
():
def
create_nonce
(
size
=
32
):
return
uuid
.
uuid4
().
bytes
+
uuid
.
uuid4
().
bytes
# seems we need at least 32 bytes not 16 as python gives us...
return
os
.
urandom
(
size
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment