Skip to content

Commit 10131b7

Browse files
author
Thilo Cestonaro
committed
Added security policy Basic256Sha256
Added deprecated warning to the implementation of Basic256 and Basic128Rsa15 security policies. Removed usage of security policies Basic256 and Basic128Rsa15 from server example implementation. Basic256 and Basic128Rsa15 use both the sha-1 algorithm. This is considererd not secure anymore since OPC UA Spec 1.04. See: http://opcfoundation.org/UA-Profile/UA/SecurityPolicy%23Basic128Rsa15 http://opcfoundation.org/UA-Profile/UA/SecurityPolicy%23Basic256 For details on Basic256Sha256 security policy have a look here: http://opcfoundation.org/UA-Profile/UA/SecurityPolicy%23Basic256Sha256
1 parent b934a49 commit 10131b7

10 files changed

Lines changed: 223 additions & 97 deletions

File tree

examples/client_to_prosys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def event_notification(self, event):
2525
logging.basicConfig(level=logging.DEBUG)
2626
client = Client("opc.tcp://localhost:53530/OPCUA/SimulationServer/")
2727
#client = Client("opc.tcp://olivier:olivierpass@localhost:53530/OPCUA/SimulationServer/")
28-
#client.set_security_string("Basic256,SignAndEncrypt,certificate-example.der,private-key-example.pem")
28+
#client.set_security_string("Basic256Sha256,SignAndEncrypt,certificate-example.der,private-key-example.pem")
2929
try:
3030
client.connect()
3131
root = client.get_root_node()

examples/client_to_prosys_crypto.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
if __name__ == "__main__":
1111
logging.basicConfig(level=logging.DEBUG)
1212
client = Client("opc.tcp://localhost:53530/OPCUA/SimulationServer/")
13-
client.set_security_string("Basic256,Sign,certificate-example.der,private-key-example.pem")
13+
client.set_security_string("Basic256Sha256,Sign,certificate-example.der,private-key-example.pem")
1414
try:
1515
client.connect()
1616
root = client.get_root_node()

examples/server-example.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,8 @@ def run(self):
9191
# set all possible endpoint policies for clients to connect through
9292
server.set_security_policy([
9393
ua.SecurityPolicyType.NoSecurity,
94-
ua.SecurityPolicyType.Basic128Rsa15_SignAndEncrypt,
95-
ua.SecurityPolicyType.Basic128Rsa15_Sign,
96-
ua.SecurityPolicyType.Basic256_SignAndEncrypt,
97-
ua.SecurityPolicyType.Basic256_Sign])
94+
ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt,
95+
ua.SecurityPolicyType.Basic256Sha256_Sign])
9896

9997
# setup our own namespace
10098
uri = "http://examples.freeopcua.github.io"

opcua/client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def set_security_string(self, string):
159159
"""
160160
Set SecureConnection mode. String format:
161161
Policy,Mode,certificate,private_key[,server_private_key]
162-
where Policy is Basic128Rsa15 or Basic256,
162+
where Policy is Basic128Rsa15, Basic256 or Basic256Sha256,
163163
Mode is Sign or SignAndEncrypt
164164
certificate, private_key and server_private_key are
165165
paths to .pem or .der files

opcua/crypto/security_policies.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
from abc import ABCMeta, abstractmethod
24
from opcua.ua import CryptographyNone, SecurityPolicy
35
from opcua.ua import MessageSecurityMode
@@ -307,9 +309,63 @@ def encrypted_block_size(self):
307309
def decrypt(self, data):
308310
return uacrypto.cipher_decrypt(self.cipher, data)
309311

312+
class SignerSha256(Signer):
313+
314+
def __init__(self, client_pk):
315+
require_cryptography(self)
316+
self.client_pk = client_pk
317+
self.key_size = self.client_pk.key_size // 8
318+
319+
def signature_size(self):
320+
return self.key_size
321+
322+
def signature(self, data):
323+
return uacrypto.sign_sha256(self.client_pk, data)
324+
325+
class VerifierSha256(Verifier):
326+
327+
def __init__(self, server_cert):
328+
require_cryptography(self)
329+
self.server_cert = server_cert
330+
self.key_size = self.server_cert.public_key().key_size // 8
331+
332+
def signature_size(self):
333+
return self.key_size
334+
335+
def verify(self, data, signature):
336+
uacrypto.verify_sha256(self.server_cert, data, signature)
337+
338+
class SignerHMac256(Signer):
339+
340+
def __init__(self, key):
341+
require_cryptography(self)
342+
self.key = key
343+
344+
def signature_size(self):
345+
return uacrypto.sha256_size()
346+
347+
def signature(self, data):
348+
return uacrypto.hmac_sha256(self.key, data)
349+
350+
351+
class VerifierHMac256(Verifier):
352+
353+
def __init__(self, key):
354+
require_cryptography(self)
355+
self.key = key
356+
357+
def signature_size(self):
358+
return uacrypto.sha256_size()
359+
360+
def verify(self, data, signature):
361+
expected = uacrypto.hmac_sha256(self.key, data)
362+
if signature != expected:
363+
raise uacrypto.InvalidSignature
310364

311365
class SecurityPolicyBasic128Rsa15(SecurityPolicy):
312366
"""
367+
DEPRECATED, do not use anymore!
368+
313369
Security Basic 128Rsa15
314370
A suite of algorithms that uses RSA15 as Key-Wrap-algorithm
315371
and 128-Bit (16 bytes) for encryption algorithms.
@@ -344,6 +400,9 @@ def encrypt_asymmetric(pubkey, data):
344400
return uacrypto.encrypt_rsa15(pubkey, data)
345401

346402
def __init__(self, server_cert, client_cert, client_pk, mode):
403+
logger = logging.getLogger(__name__)
404+
logger.warning("DEPRECATED! Do not use SecurityPolicyBasic128Rsa15 anymore!")
405+
347406
require_cryptography(self)
348407
if isinstance(server_cert, bytes):
349408
server_cert = uacrypto.x509_from_der(server_cert)
@@ -376,6 +435,8 @@ def make_symmetric_key(self, nonce1, nonce2):
376435

377436
class SecurityPolicyBasic256(SecurityPolicy):
378437
"""
438+
DEPRECATED, do not use anymore!
439+
379440
Security Basic 256
380441
A suite of algorithms that are for 256-Bit (32 bytes) encryption,
381442
algorithms include:
@@ -410,6 +471,9 @@ def encrypt_asymmetric(pubkey, data):
410471
return uacrypto.encrypt_rsa_oaep(pubkey, data)
411472

412473
def __init__(self, server_cert, client_cert, client_pk, mode):
474+
logger = logging.getLogger(__name__)
475+
logger.warning("DEPRECATED! Do not use SecurityPolicyBasic256 anymore!")
476+
413477
require_cryptography(self)
414478
if isinstance(server_cert, bytes):
415479
server_cert = uacrypto.x509_from_der(server_cert)
@@ -440,14 +504,78 @@ def make_symmetric_key(self, nonce1, nonce2):
440504
self.symmetric_cryptography.Verifier = VerifierAesCbc(sigkey)
441505
self.symmetric_cryptography.Decryptor = DecryptorAesCbc(key, init_vec)
442506

507+
class SecurityPolicyBasic256Sha256(SecurityPolicy):
508+
"""
509+
Security Basic 256Sha256
510+
A suite of algorithms that uses Sha256 as Key-Wrap-algorithm
511+
and 256-Bit (32 bytes) for encryption algorithms.
512+
513+
- SymmetricSignatureAlgorithm_HMAC-SHA2-256
514+
https://tools.ietf.org/html/rfc4634
515+
- SymmetricEncryptionAlgorithm_AES256-CBC
516+
http://www.w3.org/2001/04/xmlenc#aes256-cbc
517+
- AsymmetricSignatureAlgorithm_RSA-PKCS15-SHA2-256
518+
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
519+
- AsymmetricEncryptionAlgorithm_RSA-OAEP-SHA1
520+
http://www.w3.org/2001/04/xmlenc#rsa-oaep
521+
- KeyDerivationAlgorithm_P-SHA2-256
522+
http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha256
523+
- CertificateSignatureAlgorithm_RSA-PKCS15-SHA2-256
524+
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
525+
- Basic256Sha256_Limits
526+
-> DerivedSignatureKeyLength: 256 bits
527+
-> MinAsymmetricKeyLength: 2048 bits
528+
-> MaxAsymmetricKeyLength: 4096 bits
529+
-> SecureChannelNonceLength: 32 bytes
530+
"""
531+
532+
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
533+
signature_key_size = 32
534+
symmetric_key_size = 32
535+
AsymmetricEncryptionURI = "http://www.w3.org/2001/04/xmlenc#rsa-oaep"
536+
537+
@staticmethod
538+
def encrypt_asymmetric(pubkey, data):
539+
return uacrypto.encrypt_rsa_oaep(pubkey, data)
540+
541+
def __init__(self, server_cert, client_cert, client_pk, mode):
542+
require_cryptography(self)
543+
if isinstance(server_cert, bytes):
544+
server_cert = uacrypto.x509_from_der(server_cert)
545+
# even in Sign mode we need to asymmetrically encrypt secrets
546+
# transmitted in OpenSecureChannel. So SignAndEncrypt here
547+
self.asymmetric_cryptography = Cryptography(
548+
MessageSecurityMode.SignAndEncrypt)
549+
self.asymmetric_cryptography.Signer = SignerSha256(client_pk)
550+
self.asymmetric_cryptography.Verifier = VerifierSha256(server_cert)
551+
self.asymmetric_cryptography.Encryptor = EncryptorRsa(
552+
server_cert, uacrypto.encrypt_rsa_oaep, 42)
553+
self.asymmetric_cryptography.Decryptor = DecryptorRsa(
554+
client_pk, uacrypto.decrypt_rsa_oaep, 42)
555+
self.symmetric_cryptography = Cryptography(mode)
556+
self.Mode = mode
557+
self.server_certificate = uacrypto.der_from_x509(server_cert)
558+
self.client_certificate = uacrypto.der_from_x509(client_cert)
559+
560+
def make_symmetric_key(self, nonce1, nonce2):
561+
# specs part 6, 6.7.5
562+
key_sizes = (self.signature_key_size, self.symmetric_key_size, 16)
563+
564+
(sigkey, key, init_vec) = uacrypto.p_sha256(nonce2, nonce1, key_sizes)
565+
self.symmetric_cryptography.Signer = SignerHMac256(sigkey)
566+
self.symmetric_cryptography.Encryptor = EncryptorAesCbc(key, init_vec)
567+
568+
(sigkey, key, init_vec) = uacrypto.p_sha256(nonce1, nonce2, key_sizes)
569+
self.symmetric_cryptography.Verifier = VerifierHMac256(sigkey)
570+
self.symmetric_cryptography.Decryptor = DecryptorAesCbc(key, init_vec)
443571

444572
def encrypt_asymmetric(pubkey, data, policy_uri):
445573
"""
446574
Encrypt data with pubkey using an asymmetric algorithm.
447575
The algorithm is selected by policy_uri.
448576
Returns a tuple (encrypted_data, algorithm_uri)
449577
"""
450-
for cls in [SecurityPolicyBasic256, SecurityPolicyBasic128Rsa15]:
578+
for cls in [SecurityPolicyBasic256Sha256, SecurityPolicyBasic256, SecurityPolicyBasic128Rsa15]:
451579
if policy_uri == cls.URI:
452580
return (cls.encrypt_asymmetric(pubkey, data),
453581
cls.AsymmetricEncryptionURI)

opcua/crypto/uacrypto.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,27 @@ def sign_sha1(private_key, data):
4949
hashes.SHA1()
5050
)
5151

52+
def sign_sha256(private_key, data):
53+
return private_key.sign(
54+
data,
55+
padding.PKCS1v15(),
56+
hashes.SHA256()
57+
)
58+
5259
def verify_sha1(certificate, data, signature):
5360
certificate.public_key().verify(
5461
signature,
5562
data,
5663
padding.PKCS1v15(),
5764
hashes.SHA1())
5865

66+
def verify_sha256(certificate, data, signature):
67+
certificate.public_key().verify(
68+
signature,
69+
data,
70+
padding.PKCS1v15(),
71+
hashes.SHA256())
72+
5973
def encrypt_basic256(public_key, data):
6074
ciphertext = public_key.encrypt(
6175
data,
@@ -124,10 +138,16 @@ def hmac_sha1(key, message):
124138
hasher.update(message)
125139
return hasher.finalize()
126140

141+
def hmac_sha256(key, message):
142+
hasher = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
143+
hasher.update(message)
144+
return hasher.finalize()
127145

128146
def sha1_size():
129147
return hashes.SHA1.digest_size
130148

149+
def sha256_size():
150+
return hashes.SHA256.digest_size
131151

132152
def p_sha1(secret, seed, sizes=()):
133153
"""
@@ -151,6 +171,27 @@ def p_sha1(secret, seed, sizes=()):
151171
result = result[size:]
152172
return tuple(parts)
153173

174+
def p_sha256(secret, seed, sizes=()):
175+
"""
176+
Derive one or more keys from secret and seed.
177+
(See specs part 6, 6.7.5 and RFC 2246 - TLS v1.0)
178+
Lengths of keys will match sizes argument
179+
"""
180+
full_size = 0
181+
for size in sizes:
182+
full_size += size
183+
184+
result = b''
185+
accum = seed
186+
while len(result) < full_size:
187+
accum = hmac_sha256(secret, accum)
188+
result += hmac_sha256(secret, accum + seed)
189+
190+
parts = []
191+
for size in sizes:
192+
parts.append(result[:size])
193+
result = result[size:]
194+
return tuple(parts)
154195

155196
def x509_name_to_string(name):
156197
parts = ["{0}={1}".format(attr.oid._name, attr.value) for attr in name]
@@ -174,6 +215,6 @@ def x509_to_string(cert):
174215
cert = load_certificate("../examples/server_cert.pem")
175216
#rsa_pubkey = pubkey_from_dercert(der)
176217
rsa_privkey = load_private_key("../examples/mykey.pem")
177-
218+
178219
from IPython import embed
179220
embed()

0 commit comments

Comments
 (0)