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
import
opcua.uaprotocol
as
ua
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
):
"""
handle socket connection and send ua messages
...
...
@@ -111,62 +102,21 @@ class UASocketClient(object):
break
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
):
body_chunk
=
b""
while
True
:
ret
=
self
.
_receive_complete_msg
()
if
ret
is
None
:
return
hdr
,
algohdr
,
seqhdr
,
body
=
ret
if
hdr
.
ChunkType
in
(
b"F"
,
b"A"
):
body
.
data
=
body_chunk
+
body
.
data
break
elif
hdr
.
ChunkType
==
b"C"
:
self
.
logger
.
debug
(
"Received an intermediate message with header %s, waiting for next message"
,
hdr
)
body_chunk
+=
body
.
data
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
)
msg
=
ua
.
tcp_message_from_socket
(
self
.
_security_policy
,
self
.
_socket
)
if
isinstance
(
msg
,
ua
.
MessageChunk
):
chunks
=
[
msg
]
# TODO: check everything
while
chunks
[
-
1
].
MessageHeader
.
ChunkType
==
ua
.
ChunkType
.
Intermediate
:
chunks
.
append
(
ua
.
tcp_message_from_socket
(
self
.
_security_policy
,
self
.
_socket
))
body
=
b""
.
join
([
c
.
Body
for
c
in
chunks
])
self
.
_call_callback
(
msg
.
SequenceHeader
.
RequestId
,
utils
.
Buffer
(
body
))
elif
isinstance
(
msg
,
ua
.
Acknowledge
):
self
.
_call_callback
(
0
,
msg
)
elif
isinstance
(
msg
,
ua
.
ErrorMessage
):
self
.
logger
.
warning
(
"Received an error: {}"
.
format
(
msg
))
else
:
self
.
logger
.
warning
(
"Unsupported message type: %s"
,
header
.
MessageType
)
return
None
seqhdr
=
ua
.
SequenceHeader
.
from_binary
(
body
)
self
.
logger
.
debug
(
seqhdr
)
return
header
,
algohdr
,
seqhdr
,
body
raise
Exception
(
"Unsupported message type: {}"
.
format
(
msg
))
def
_call_callback
(
self
,
request_id
,
body
):
with
self
.
_lock
:
...
...
@@ -219,7 +169,7 @@ class UASocketClient(object):
with
self
.
_lock
:
self
.
_callbackmap
[
0
]
=
future
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
return
ack
...
...
@@ -262,18 +212,19 @@ class BinaryClient(object):
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
.
_publishcallbacks
=
{}
self
.
_lock
=
Lock
()
self
.
_timeout
=
timeout
self
.
_uasocket
=
None
self
.
_security_policy
=
security_policy
def
connect_socket
(
self
,
host
,
port
):
"""
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
)
def
disconnect_socket
(
self
):
...
...
opcua/client.py
View file @
be43b413
...
...
@@ -67,7 +67,7 @@ class Client(object):
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.
if you are unsure of url, write at least hostname and port
...
...
@@ -82,8 +82,7 @@ class Client(object):
self
.
description
=
self
.
name
self
.
application_uri
=
"urn:freeopcua:client"
self
.
product_uri
=
"urn:freeopcua.github.no:client"
self
.
security_policy_uri
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
self
.
security_mode
=
ua
.
MessageSecurityMode
.
None_
self
.
security_policy
=
security_policy
self
.
secure_channel_id
=
None
self
.
default_timeout
=
3600000
self
.
secure_channel_timeout
=
self
.
default_timeout
...
...
@@ -92,8 +91,7 @@ class Client(object):
self
.
server_certificate
=
b""
self
.
client_certificate
=
b""
self
.
private_key
=
b""
self
.
bclient
=
BinaryClient
(
timeout
)
self
.
_nonce
=
None
self
.
bclient
=
BinaryClient
(
timeout
,
security_policy
=
security_policy
)
self
.
_session_counter
=
1
self
.
keepalive
=
None
...
...
@@ -192,10 +190,12 @@ class Client(object):
params
.
RequestType
=
ua
.
SecurityTokenRequestType
.
Issue
if
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
.
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
)
self
.
security_policy
.
make_symmetric_key
(
nonce
,
result
.
ServerNonce
)
self
.
secure_channel_timeout
=
result
.
SecurityToken
.
RevisedLifetime
def
close_secure_channel
(
self
):
...
...
@@ -251,18 +251,20 @@ class Client(object):
desc
.
ApplicationType
=
ua
.
ApplicationType
.
Client
params
=
ua
.
CreateSessionParameters
()
params
.
ClientNonce
=
utils
.
create_nonce
()
params
.
ClientCertificate
=
b''
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
.
ClientNonce
=
nonce
params
.
ClientCertificate
=
self
.
security_policy
.
client_certificate
params
.
ClientDescription
=
desc
params
.
EndpointUrl
=
self
.
server_url
.
geturl
()
params
.
SessionName
=
self
.
description
+
" Session"
+
str
(
self
.
_session_counter
)
params
.
RequestedSessionTimeout
=
3600000
params
.
MaxResponseMessageSize
=
0
# means no max size
params
.
ClientCertificate
=
self
.
client_certificate
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
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()
self
.
_policy_ids
=
ep
.
UserIdentityTokens
self
.
session_timeout
=
response
.
RevisedSessionTimeout
...
...
@@ -285,6 +287,9 @@ class Client(object):
Activate session using either username and password or private_key
"""
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"
)
if
not
username
and
not
certificate
:
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
from
opcua
import
Server
from
opcua
import
Node
from
opcua
import
uamethod
from
opcua
import
security_policies
def
add_minimum_args
(
parser
):
...
...
@@ -58,7 +59,29 @@ def add_common_args(parser):
type
=
int
,
default
=
0
,
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
):
args
=
parser
.
parse_args
()
...
...
@@ -66,6 +89,8 @@ def parse_args(parser, requirenodeid=False):
if
args
.
url
and
'://'
not
in
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
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...
if
requirenodeid
and
args
.
nodeid
==
"i=84"
and
args
.
path
==
""
:
parser
.
print_usage
()
...
...
@@ -99,7 +124,7 @@ def uaread():
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
()
try
:
node
=
get_node
(
client
,
args
)
...
...
@@ -235,7 +260,7 @@ def uawrite():
metavar
=
"VALUE"
)
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
()
try
:
node
=
get_node
(
client
,
args
)
...
...
@@ -266,7 +291,7 @@ def uals():
if
args
.
long_format
is
None
:
args
.
long_format
=
1
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
try
:
node
=
get_node
(
client
,
args
)
...
...
@@ -351,7 +376,7 @@ def uasubscribe():
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
()
try
:
node
=
get_node
(
client
,
args
)
...
...
@@ -432,7 +457,7 @@ def uaclient():
help
=
"set client private key"
)
args
=
parse_args
(
parser
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
)
client
=
Client
(
args
.
url
,
timeout
=
args
.
timeout
,
security_policy
=
args
.
security
)
client
.
connect
()
if
args
.
certificate
:
client
.
load_client_certificate
(
args
.
certificate
)
...
...
@@ -593,7 +618,7 @@ def uahistoryread():
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
()
try
:
node
=
get_node
(
client
,
args
)
...
...
opcua/uaprotocol_hand.py
View file @
be43b413
...
...
@@ -254,7 +254,7 @@ class CryptographyNone:
"""
Base class for symmetric/asymmetric cryprography
"""
def
__init__
(
self
,
mode
=
auto
.
MessageSecurityMode
.
None_
):
def
__init__
(
self
):
pass
def
plain_block_size
(
self
):
...
...
@@ -309,17 +309,17 @@ class SecurityPolicy:
"""
Base class for security policy
"""
URI
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
signature_key_size
=
0
symmetric_key_size
=
0
def
__init__
(
self
):
self
.
asymmetric_cryptography
=
CryptographyNone
()
self
.
symmetric_cryptography
=
CryptographyNone
()
self
.
Mode
=
auto
.
MessageSecurityMode
.
None_
self
.
URI
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
self
.
server_certificate
=
b""
self
.
client_certificate
=
b""
def
symmetric_key_size
(
self
):
return
0
def
make_symmetric_key
(
self
,
a
,
b
):
pass
...
...
@@ -433,6 +433,33 @@ class MessageChunk(uatypes.FrozenClass):
__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
ana
=
auto
.
NodeAttributesMask
...
...
opcua/utils.py
View file @
be43b413
import
logging
import
uuid
import
os
from
concurrent.futures
import
Future
import
functools
import
threading
...
...
@@ -108,8 +108,8 @@ class SocketWrapper(object):
def
create_nonce
():
return
uuid
.
uuid4
().
bytes
+
uuid
.
uuid4
().
bytes
# seems we need at least 32 bytes not 16 as python gives us...
def
create_nonce
(
size
=
32
):
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