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

adds security policy Aes128-Sha256-RsaOaep #1032

Merged
merged 12 commits into from
Sep 16, 2022
2 changes: 1 addition & 1 deletion asyncua/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ async def open_secure_channel(self, renew=False):
params.SecurityMode = self.security_policy.Mode
params.RequestedLifetime = self.secure_channel_timeout
# length should be equal to the length of key of symmetric encryption
params.ClientNonce = create_nonce(self.security_policy.symmetric_key_size)
params.ClientNonce = create_nonce(self.security_policy.secure_channel_nonce_length)
result = await self.uaclient.open_secure_channel(params)
if self.secure_channel_timeout != result.SecurityToken.RevisedLifetime:
_logger.info("Requested secure channel timeout to be %dms, got %dms instead", self.secure_channel_timeout, result.SecurityToken.RevisedLifetime)
Expand Down
2 changes: 1 addition & 1 deletion asyncua/common/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def open(self, params, server):
Called on server side to open secure channel.
"""

self.local_nonce = ua.utils.create_nonce(self.security_policy.symmetric_key_size)
self.local_nonce = ua.utils.create_nonce(self.security_policy.secure_channel_nonce_length)
self.remote_nonce = params.ClientNonce
response = ua.OpenSecureChannelResult()
response.ServerNonce = self.local_nonce
Expand Down
92 changes: 91 additions & 1 deletion asyncua/crypto/security_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,92 @@ def verify(self, data, signature):
raise uacrypto.InvalidSignature


class SecurityPolicyAes128Sha256RsaOaep(SecurityPolicy):
"""
Security Aes128 Sha256 RsaOaep
A suite of algorithms that uses Sha256 as Key-Wrap-algorithm
and 128-Bit (16 bytes) for encryption algorithms.

- SymmetricSignatureAlgorithm_HMAC-SHA2-256
https://tools.ietf.org/html/rfc4634
- SymmetricEncryptionAlgorithm_AES128-CBC
http://www.w3.org/2001/04/xmlenc#aes256-cbc
- AsymmetricSignatureAlgorithm_RSA-PKCS15-SHA2-256
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- AsymmetricEncryptionAlgorithm_RSA-OAEP-SHA1
http://www.w3.org/2001/04/xmlenc#rsa-oaep
- KeyDerivationAlgorithm_P-SHA2-256
http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha256
- CertificateSignatureAlgorithm_RSA-PKCS15-SHA2-256
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- Aes128Sha256RsaOaep_Limits
-> DerivedSignatureKeyLength: 256 bits
-> MinAsymmetricKeyLength: 2048 bits
-> MaxAsymmetricKeyLength: 4096 bits
-> SecureChannelNonceLength: 32 bytes
"""

URI = "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"
signature_key_size = 32
symmetric_key_size = 16
secure_channel_nonce_length = 32
AsymmetricEncryptionURI = "http://www.w3.org/2001/04/xmlenc#rsa-oaep"
AsymmetricSignatureURI = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"

@staticmethod
def encrypt_asymmetric(pubkey, data):
return uacrypto.encrypt_rsa_oaep(pubkey, data)

def __init__(self, peer_cert, host_cert, client_pk, mode,
permission_ruleset=None):
require_cryptography(self)
if isinstance(peer_cert, bytes):
peer_cert = uacrypto.x509_from_der(peer_cert)
# even in Sign mode we need to asymmetrically encrypt secrets
# transmitted in OpenSecureChannel. So SignAndEncrypt here
self.asymmetric_cryptography = Cryptography(
MessageSecurityMode.SignAndEncrypt)
self.asymmetric_cryptography.Signer = SignerSha256(client_pk)
self.asymmetric_cryptography.Verifier = VerifierSha256(peer_cert)
self.asymmetric_cryptography.Encryptor = EncryptorRsa(
peer_cert, uacrypto.encrypt_rsa_oaep, 42)
self.asymmetric_cryptography.Decryptor = DecryptorRsa(
client_pk, uacrypto.decrypt_rsa_oaep, 42)
self.symmetric_cryptography = Cryptography(mode)
self.Mode = mode
self.peer_certificate = uacrypto.der_from_x509(peer_cert)
self.host_certificate = uacrypto.der_from_x509(host_cert)
if permission_ruleset is None:
from asyncua.crypto.permission_rules import SimpleRoleRuleset
permission_ruleset = SimpleRoleRuleset()

self.permissions = permission_ruleset

def make_local_symmetric_key(self, secret, seed):
# specs part 6, 6.7.5
key_sizes = (self.signature_key_size, self.symmetric_key_size, 16)

(sigkey, key, init_vec) = uacrypto.p_sha256(secret, seed, key_sizes)
self.symmetric_cryptography.Signer = SignerHMac256(sigkey)
self.symmetric_cryptography.Encryptor = EncryptorAesCbc(key, init_vec)

def make_remote_symmetric_key(self, secret, seed, lifetime):

# specs part 6, 6.7.5
key_sizes = (self.signature_key_size, self.symmetric_key_size, 16)

(sigkey, key, init_vec) = uacrypto.p_sha256(secret, seed, key_sizes)
if self.symmetric_cryptography.Verifier or self.symmetric_cryptography.Decryptor:
self.symmetric_cryptography.Prev_Verifier = self.symmetric_cryptography.Verifier
self.symmetric_cryptography.Prev_Decryptor = self.symmetric_cryptography.Decryptor
self.symmetric_cryptography.prev_key_expiration = self.symmetric_cryptography.key_expiration

# lifetime is in ms
self.symmetric_cryptography.key_expiration = time.time() + (lifetime * 0.001)
self.symmetric_cryptography.Verifier = VerifierHMac256(sigkey)
self.symmetric_cryptography.Decryptor = DecryptorAesCbc(key, init_vec)


class SecurityPolicyBasic128Rsa15(SecurityPolicy):
"""
DEPRECATED, do not use anymore!
Expand Down Expand Up @@ -458,6 +544,7 @@ class SecurityPolicyBasic128Rsa15(SecurityPolicy):
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"
signature_key_size = 16
symmetric_key_size = 16
secure_channel_nonce_length = 16
AsymmetricEncryptionURI = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
AsymmetricSignatureURI = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"

Expand Down Expand Up @@ -545,6 +632,7 @@ class SecurityPolicyBasic256(SecurityPolicy):
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic256"
signature_key_size = 24
symmetric_key_size = 32
secure_channel_nonce_length = 32
AsymmetricEncryptionURI = "http://www.w3.org/2001/04/xmlenc#rsa-oaep"
AsymmetricSignatureURI = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"

Expand Down Expand Up @@ -633,6 +721,7 @@ class SecurityPolicyBasic256Sha256(SecurityPolicy):
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
signature_key_size = 32
symmetric_key_size = 32
secure_channel_nonce_length = 32
AsymmetricEncryptionURI = "http://www.w3.org/2001/04/xmlenc#rsa-oaep"
AsymmetricSignatureURI = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"

Expand Down Expand Up @@ -696,7 +785,8 @@ def encrypt_asymmetric(pubkey, data, policy_uri):
The algorithm is selected by policy_uri.
Returns a tuple (encrypted_data, algorithm_uri)
"""
for cls in [SecurityPolicyBasic256Sha256, SecurityPolicyBasic256, SecurityPolicyBasic128Rsa15]:
for cls in [SecurityPolicyBasic256Sha256, SecurityPolicyBasic256,
SecurityPolicyBasic128Rsa15, SecurityPolicyAes128Sha256RsaOaep]:
if policy_uri == cls.URI:
return (cls.encrypt_asymmetric(pubkey, data),
cls.AsymmetricEncryptionURI)
Expand Down
19 changes: 17 additions & 2 deletions asyncua/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ def __init__(self, iserver: InternalServer = None, user_manager=None):
# enable all endpoints by default
self._security_policy = [
ua.SecurityPolicyType.NoSecurity, ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt,
ua.SecurityPolicyType.Basic256Sha256_Sign
ua.SecurityPolicyType.Basic256Sha256_Sign, ua.SecurityPolicyType.Aes128Sha256RsaOaep_SignAndEncrypt,
ua.SecurityPolicyType.Aes128Sha256RsaOaep_Sign
]
# allow all certificates by default
self._permission_ruleset = None
self._policyIDs = ["Anonymous", "Basic256Sha256", "Username"]
self._policyIDs = ["Anonymous", "Basic256Sha256", "Username", "Aes128Sha256RsaOaep"]
self.certificate = None

async def init(self, shelf_file=None):
Expand Down Expand Up @@ -354,6 +355,20 @@ async def _setup_server_nodes(self):
ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic256Sha256,
ua.MessageSecurityMode.Sign, self.certificate, self.iserver.private_key,
permission_ruleset=self._permission_ruleset))
if ua.SecurityPolicyType.Aes128Sha256RsaOaep_SignAndEncrypt in self._security_policy:
self._set_endpoints(security_policies.SecurityPolicyAes128Sha256RsaOaep,
ua.MessageSecurityMode.SignAndEncrypt)
self._policies.append(
ua.SecurityPolicyFactory(security_policies.SecurityPolicyAes128Sha256RsaOaep,
ua.MessageSecurityMode.SignAndEncrypt, self.certificate,
self.iserver.private_key,
permission_ruleset=self._permission_ruleset))
if ua.SecurityPolicyType.Aes128Sha256RsaOaep_Sign in self._security_policy:
self._set_endpoints(security_policies.SecurityPolicyAes128Sha256RsaOaep, ua.MessageSecurityMode.Sign)
self._policies.append(
ua.SecurityPolicyFactory(security_policies.SecurityPolicyAes128Sha256RsaOaep,
ua.MessageSecurityMode.Sign, self.certificate, self.iserver.private_key,
permission_ruleset=self._permission_ruleset))

def _set_endpoints(self, policy=ua.SecurityPolicy, mode=ua.MessageSecurityMode.None_):
idtokens = []
Expand Down
1 change: 1 addition & 0 deletions asyncua/ua/uaprotocol_hand.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class SecurityPolicy:
AsymmetricSignatureURI: str = ''
signature_key_size: int = 0
symmetric_key_size: int = 0
secure_channel_nonce_length: int = 0

def __init__(self, permissions=None):
self.asymmetric_cryptography = CryptographyNone()
Expand Down
2 changes: 2 additions & 0 deletions asyncua/ua/uatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1157,3 +1157,5 @@ class SecurityPolicyType(Enum):
Basic256_SignAndEncrypt = 4
Basic256Sha256_Sign = 5
Basic256Sha256_SignAndEncrypt = 6
Aes128Sha256RsaOaep_Sign = 7
Aes128Sha256RsaOaep_SignAndEncrypt = 8
16 changes: 16 additions & 0 deletions tests/test_crypto_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,22 @@ async def test_basic256_encrypt_fail(srv_crypto_all_certs):
)


async def test_Aes128Sha256RsaOaep_encrypt_success(srv_crypto_all_certs):
clt = Client(uri_crypto)
_, cert = srv_crypto_all_certs
await clt.set_security(
security_policies.SecurityPolicyAes128Sha256RsaOaep,
f"{EXAMPLE_PATH}certificate-example.der",
f"{EXAMPLE_PATH}private-key-example.pem",
None,
cert,
ua.MessageSecurityMode.SignAndEncrypt
)

async with clt:
assert await clt.nodes.objects.get_children()


async def test_certificate_handling_success(srv_crypto_one_cert):
_, cert = srv_crypto_one_cert
clt = Client(uri_crypto_cert)
Expand Down