nauful / LibUA

Open-source OPC UA client and server library
Apache License 2.0
262 stars 94 forks source link

Can not authenticate using SecurityPolicy Basic256 #13

Closed Ttberg closed 4 years ago

Ttberg commented 4 years ago

I was unable to authenticate using the SecurityPolicy Basic256 and the reference server of the OPC foundation.

I was able to open a secure channel and creating a session using: m_client.OpenSecureChannel(MessageSecurityMode.SignAndEncrypt, SecurityPolicy.Basic256, serverCert); m_client.CreateSession(appDesc, "urn:Application", 2);

But activating the session using: m_client.ActivateSession( new UserIdentityUsernameToken(usernamePolicyDesc, "Name", (new UTF8Encoding()).GetBytes("password"), "http://www.w3.org/2001/04/xmlenc#rsa-oaep"), new[] { "en" });

did not work.

After looking at the code to activate the session, shouldn't the actual SecurityPolicy (which is available in the config member) be used when calculating the padding size and when encrypting the password instead of the hard coded Basic128Rsa15 SecurityPolicy?

nauful commented 4 years ago

Security for session activation (secrets exchange) was fixed to Basic128Rsa15 according to the spec when implemented ~2015, but I'll take a look and see what the current behaviour is now.

nauful commented 4 years ago

image

18-Apr-20 13:25:23.430 ***EXCEPTION*** BadSecurityPolicyRejected 'The security policy is not supported.' The security policy is not supported.
18-Apr-20 13:25:23.446 TCPSERVERCHANNEL ForceChannelFault Socket=000AF38C, ChannelId=0, TokenId=0, Reason=Id: BadSecurityPolicyRejected
Description: Could not verify security on OpenSecureChannel request.
===
Id: BadSecurityPolicyRejected
Description: The security policy is not supported.
>>> The security policy is not supported.
---    at Opc.Ua.Bindings.UaSCUaBinaryChannel.ReadAsymmetricMessage(ArraySegment`1 buffer, X509Certificate2 receiverCertificate, UInt32& channelId, X509Certificate2& senderCertificate, UInt32& requestId, UInt32& sequenceNumber)
---    at Opc.Ua.Bindings.TcpServerChannel.ProcessOpenSecureChannelRequest(UInt32 messageType, ArraySegment`1 messageChunk)
18-Apr-20 13:25:23.452 Channel 16: SendErrorMessage()
18-Apr-20 13:25:23.458 Channel 16 in Faulted state.

The latest OPC Foundation reference server doesn't seem to expose Basic256 as a security policy for its endpoint. I've tested an older version which did and it works. Although the latest OPC Foundation policy may have changed, the previous behaviour was for any security policy other than None, Basic128Rsa15 is used to exchange the secret key used for further encryption according to the selected policy.

Could you please share the client code that you are using until the line that fails, and a zipped version of source for the UA server so I can investigate the point at which it rejects the request?

nauful commented 4 years ago

For reference, to connect as Basic256: Check that you have this policy (not sure if it works with the latest reference server sample):

<ServerSecurityPolicy>
    <SecurityMode>SignAndEncrypt_3</SecurityMode>
    <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256</SecurityPolicyUri>
</ServerSecurityPolicy>

image

The following code allows me to connect:

var appDesc = new ApplicationDescription(
  "urn:qs:DemoClient", "uri:qs:DemoClient", new LocalizedText("UA SDK client"),
  ApplicationType.Client, null, null, null);

ApplicationDescription[] appDescs = null;
EndpointDescription[] endpointDescs = null;

var client = new DemoClient("127.0.0.1", 51210, 1000);
client.Connect();
client.OpenSecureChannel(MessageSecurityMode.None, SecurityPolicy.None, null);
client.FindServers(out appDescs, new[] { "en" });
client.GetEndpoints(out endpointDescs, new[] { "en" });
client.Disconnect();

// Check matching message security mode and security policy too
// Lazy way to find server certificate is just grab any endpoint with one
byte[] serverCert = endpointDescs
  .First(e => e.ServerCertificate != null && e.ServerCertificate.Length > 0)
  .ServerCertificate;

var usernamePolicyDesc = endpointDescs
  .First(e => e.UserIdentityTokens.Any(t => t.TokenType == UserTokenType.UserName))
  .UserIdentityTokens.First(t => t.TokenType == UserTokenType.UserName)
  .PolicyId;

client = new DemoClient("127.0.0.1", 51210, 1000);
var connectRes = client.Connect();
var openRes = client.OpenSecureChannel(MessageSecurityMode.SignAndEncrypt, SecurityPolicy.Basic256, serverCert);
var createRes = client.CreateSession(appDesc, "urn:qs:DemoClient", 120);
var activateRes = client.ActivateSession(new UserIdentityAnonymousToken("0"), new[] { "en" });

/// All good from here
...
Ttberg commented 4 years ago

image

18-Apr-20 13:25:23.430 ***EXCEPTION*** BadSecurityPolicyRejected 'The security policy is not supported.' The security policy is not supported.
18-Apr-20 13:25:23.446 TCPSERVERCHANNEL ForceChannelFault Socket=000AF38C, ChannelId=0, TokenId=0, Reason=Id: BadSecurityPolicyRejected
Description: Could not verify security on OpenSecureChannel request.
===
Id: BadSecurityPolicyRejected
Description: The security policy is not supported.
>>> The security policy is not supported.
---    at Opc.Ua.Bindings.UaSCUaBinaryChannel.ReadAsymmetricMessage(ArraySegment`1 buffer, X509Certificate2 receiverCertificate, UInt32& channelId, X509Certificate2& senderCertificate, UInt32& requestId, UInt32& sequenceNumber)
---    at Opc.Ua.Bindings.TcpServerChannel.ProcessOpenSecureChannelRequest(UInt32 messageType, ArraySegment`1 messageChunk)
18-Apr-20 13:25:23.452 Channel 16: SendErrorMessage()
18-Apr-20 13:25:23.458 Channel 16 in Faulted state.

The latest OPC Foundation reference server doesn't seem to expose Basic256 as a security policy for its endpoint. I've tested an older version which did and it works. Although the latest OPC Foundation policy may have changed, the previous behaviour was for any security policy other than None, Basic128Rsa15 is used to exchange the secret key used for further encryption according to the selected policy.

Could you please share the client code that you are using until the line that fails, and a zipped version of source for the UA server so I can investigate the point at which it rejects the request?

For the server I just take the latest server code from the OPC Foundation, but I added

`

SignAndEncrypt_3 http://opcfoundation.org/UA/SecurityPolicy#Basic256

`

to the server config to enable the Basic256 security policy.

This is part of the client code I'm using: StatusCode s = m_client.Connect(); Log($"Connecting..."); if (s == StatusCode.Good) { Log($"Connected"); } else { Log($"Not connected: {s}"); } Log($"opening non secure channel"); StatusCode s2 = m_client.OpenSecureChannel(MessageSecurityMode.None, SecurityPolicy.None, null); Log($"result: {s2}"); //get all the different endpoints Log($"getting endpoints"); s2 = m_client.GetEndpoints(out m_endpointDescs, new[] { "en" }); Log($"result: {s2}"); foreach (EndpointDescription ed in m_endpointDescs) { Log($"Endpoint: {ed.EndpointUrl}, {ed.SecurityLevel}, {ed.SecurityMode}, {ed.SecurityPolicyUri}"); } Log($"disconnecting"); s2 = m_client.Disconnect(); Log($"result: {s2}"); ApplicationDescription appDesc = new ApplicationDescription("urn:OPCUAAdapter", "uri:OPCUAAdapter", new LocalizedText("OPC-UA Adapter"), ApplicationType.Client, null, null, null); // as long as there's 1 server, just use any of the server certificates byte[] serverCert = m_endpointDescs .First(e => e.ServerCertificate != null && e.ServerCertificate.Length > 0) .ServerCertificate; //connect to the client again StatusCode s3 = m_client.Connect(); Log($"result: {s3}"); if (s3 == StatusCode.Good) { Log("opening secure channel"); try { s3 = m_client.OpenSecureChannel(MessageSecurityMode.SignAndEncrypt, SecurityPolicy.Basic256, serverCert); Log("creating session"); var createRes = m_client.CreateSession(appDesc, "urn:OPCUAAdapter", 2); Log($"create session result: {createRes}"); //var activateRes = m_client.ActivateSession(new UserIdentityAnonymousToken("0"), new[] { "en" }); var usernamePolicyDesc = m_endpointDescs .First(e => e.UserIdentityTokens.Any(t => t.TokenType == UserTokenType.UserName)) .UserIdentityTokens.First(t => t.TokenType == UserTokenType.UserName && t.SecurityPolicyUri.Equals("http://opcfoundation.org/UA/SecurityPolicy#Basic256")) .PolicyId; var activateRes2 = m_client.ActivateSession( new UserIdentityUsernameToken(usernamePolicyDesc, "a username", (new UTF8Encoding()).GetBytes("a password"), "http://www.w3.org/2001/04/xmlenc#rsa-oaep"), new[] { "en" }); Log($"Activate session result: {activateRes2}"); CurrentState = State.ObtainNamespaces; } catch (Exception ex) { Log($"{ex}"); } }

Where activeRes2 equals BadUnkownResponse with the existing libua. When changing the libua code into using Basic256 as the security policy in the activate session method, the response equals Good and everything works OK

nauful commented 4 years ago

The code I pasted here works for me after adding that security configuration XML to the latest OPC Foundation reference server: https://github.com/nauful/LibUA/issues/13#issuecomment-615922123

Please confirm that it works for you, or let me know modification made it to work for you and I can merge it in.

Ttberg commented 4 years ago

Your code worked for me as well. However, you are using the UserIdentityAnonymousToken, while I'm trying to authenticate using the UserIdentityUsernameToken. The OPC reference server accepts every user as long as the password is not IsNullOrEmpty, so it doesn't really matter what you use to authenticate.

The modification I made to let this work is a change the ActivateSession method of client.cs. I've replaced all occurrences (2) of "SecurityPolicy.Basic128Rsa15" with "config.SecurityPolicy". This works when I'm using the Basic256 security policy, but I'm not sure this will work with e.g. the Basic256sha256 security policy.

nauful commented 4 years ago

I'm not sure that this is intended behaviour because according to the specification, security policy defines the transport layer security, not for encrypting an identity token. I'll verify compatibility again and commit a change if there's an issue.

By the way, I have started adding Basic256Sha256 as a security mode. The current source now fully supports this on the server side.

Ttberg commented 4 years ago

Yes I've seen you've added Basic256Sha256 as a security mode. I'm trying it now, but I'm once again unable to connect to the reference server of the OPC foundation. It's complaining about the CertificateThumbprintSize length which should be 20 but is 32. I'm looking into it right now.

By the way, have you tried using your code from 3 days ago to connect to the reference server and change the activate session argument into using a user identity token instead of the anonymous token?

nauful commented 4 years ago

The reference server seemed to accept and ignore it, although I didn't expect that the SecurityPolicy would affect anything above the transport layer.

If CertificateThumbprintSize length is wrong, try using Basic128Rsa15, or if not, Basic256Sha256 here: var thumbprint = UASecurity.RsaPkcs15Sha_Sign(new ArraySegment(signMsg), ApplicationPrivateKey, config.SecurityPolicy);

Let me know what works there.

Different UA servers seem to have different policies and don't all work with the OPC Foundation client/servers, so I've added an optional parameter userIdentitySecurityPolicy to Client.ActivateSession.

Ttberg commented 4 years ago

When trying to get Basic256Sha256 to work in my own code, I still had some changes left from trying to get the authentication to work when using SecurityPolicy Basic256. They conflicted which each other, which resulted in both not working. I went back to using your master branch and both now work fine. Basic256Sha256 is working and I'm able to authenticate.

However, I went back to try and use the version of April 19th, and the authentication did not work with that version when using Basic256 as a SecurityPolicy. Somehow after adding the Basic256Sha256 SecurityPolicy the authentication now works for both security policies.

I think this issue can now be closed. Thanks for the help.

nauful commented 4 years ago

Was probably a bug I fixed while cleaning up the security code. Sometimes the UA specification can get confusing between whether to use the security policy for symmetric wrapping or Basic128Rsa15 as the asymmetric wrapping policy in places.

Glad it works for you now.