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

X509Certificate2 in UWP #5

Open
jhalbrecht opened this issue Feb 15, 2019 · 10 comments
Open

X509Certificate2 in UWP #5

jhalbrecht opened this issue Feb 15, 2019 · 10 comments
Labels
help wanted Extra attention is needed

Comments

@jhalbrecht
Copy link
Owner

Now that I have the console app connecting to mqtt via TLS It's time to incorporate those fixes into the xamarin forms app. Messin' with the UWP app first.

Previously I referred to two certs with .crt file name extension although this wouldn't be the solution the certs loaded if I put them in the root of the UWP app and changed properties to copy to the running solution.

X509Certificate clientCert = new X509Certificate2("ca.crt");
X509Certificate caCert = X509Certificate.CreateFromCertFile("debbie.redacted.org.crt");

The solution that works in the console application refers to a .pfx file. WAG does the .pfx introduce additional requirements? <-- WAG? I doubt it, must be something I forgot!

The way that works in the console app (other than the path)

X509Certificate clientCert = new X509Certificate2("xamarinclient.pfx", "xamarin");
X509Certificate caCert = X509Certificate.CreateFromCertFile("ca.crt");

I'm looking for guidance to read more like a blog post then just the microsoft docs. A Microsoft sample that handles certificates would be cool.

@jhalbrecht jhalbrecht added the help wanted Extra attention is needed label Feb 15, 2019
@jhalbrecht
Copy link
Owner Author

I sure remember using that syntax and thinking it shouldn't because of all the UWP hoopla over AppData...

I bit the bullet and ennugetificated my Settings page with a button to load the .ca and .pfx using the jfversluis/FilePicker-Plugin-for-Xamarin-and-Windows

I'm still unsure how a real world app would handle this. I've read about pinning but it always seems to be in the context of httpclient.

@jhalbrecht
Copy link
Owner Author

Cool. This code works.

  • Is it persisted?
  • How to cram it into the X509Certificate certificates? :-)
FileData fileData = await CrossFilePicker.Current.PickFile();
if (fileData == null)
    return; // user canceled file picking

string fileName = fileData.FileName;
string contents = System.Text.Encoding.UTF8.GetString(fileData.DataArray);
string deviceFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), fileName);
File.WriteAllText(deviceFileName, contents);

@jhalbrecht
Copy link
Owner Author

Progress...

the ca.crt is string data this appears to work

X509Certificate caCert = 
    X509Certificate.CreateFromCertFile(
        Path.Combine(Environment.GetFolderPath(
            Environment.SpecialFolder.LocalApplicationData), "ca.crt"));

However the .pfx is binary data. I'm not sure if the FilePicker supports binary data?

string foo = Path.Combine(Environment.GetFolderPath(
    Environment.SpecialFolder.LocalApplicationData), "xamarinclient.pfx");
X509Certificate clientCert = 
    new X509Certificate2(foo, "xamarin");

@jhalbrecht
Copy link
Owner Author

Working on the .pfx I stored the binary .pfx file base64 encoded. I'm assuming the .pfx password is encoded in it.

Reminder this syntax works in the console app;

X509Certificate clientCert = new X509Certificate2($"c:/jstuff/tls/debbie/selfsigned/xamarinclient.pfx", "xamarin");

FileData fileData = await Plugin.FilePicker.CrossFilePicker.Current.PickFile();
if (fileData == null)
    return; // user canceled file picking

string fileName = fileData.FileName;
// string contents = System.Text.Encoding.UTF8.GetString(fileData.DataArray);

string content = Convert.ToBase64String(fileData.DataArray, 0, fileData.DataArray.Length,
                Base64FormattingOptions.None);

string deviceFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), fileName);
File.WriteAllText(deviceFileName, content);

Now to get the .pfx back out. This is where the help flag raises! This doesn't work as intended and donn't forget we need the .pfx password too.

string thePathOnDevice = Path.Combine(Environment.GetFolderPath(
    Environment.SpecialFolder.LocalApplicationData), "xamarinclient.pfx");

var theBase64EncodedPfx = File.ReadAllText(thePathOnDevice);
var toip = Convert.FromBase64String(theBase64EncodedPfx);
X509Certificate2 pfxcert = new X509Certificate2();

//splat.Import(toip, "xamarin", X509KeyStorageFlags.DefaultKeySet);
pfxcert.Import(theBase64EncodedPfx); // this complains and needs the password 'xamarin'
////X509Certificate clientCert = new X509Certificate2(foo, "xamarin");
Debug.WriteLine("fooE");
                        
// ToDo: Worth further investagation http://paulstovell.com/blog/x509certificate2

X509Certificate clientCert = new X509Certificate2(thePathOnDevice, "xamarin");

Might be handy if the showed me the equivalent constructor :-) X509Certificate is immutable on this platform. Use the equivalent constructor instead

{System.PlatformNotSupportedException: X509Certificate is immutable on this platform. Use the equivalent constructor instead.   at System.Security.Cryptography.X509Certificates.X509Certificate.Import(String fileName)   at System.Security.Cryptography.X509Certificates.X509Certificate2.Import(String fileName)   at MqttDataService.MqttDataService.Initialize()}

@jhalbrecht
Copy link
Owner Author

jhalbrecht commented Feb 17, 2019

Looking very much better. Looks like actual certificates are being made as I see the expected caCert.Issuer in both certificates. This is the .pfx code. I'm going to research X509KeyStorageFlags in X509Certificate2 clientCert = new X509Certificate2(certificate, "xamarin");

string filesDirectoryBasePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string thePfxPathOnDevice = Path.Combine(filesDirectoryBasePath, "xamarinclient.pfx");
string theBase64EncodedPfx = File.ReadAllText(thePfxPathOnDevice);
byte[] certificate = Convert.FromBase64String(theBase64EncodedPfx);
X509Certificate2 clientCert = new X509Certificate2(certificate, "xamarin");

@jhalbrecht
Copy link
Owner Author

O.K. no I don't think X509KeyStorageFlags are worth pursuing. They're for key stores which I'm not using.
Doh!

@jhalbrecht
Copy link
Owner Author

halfworksonmymachine

O.K. I've got it working if I remove some of the cert checks in the RemoteCertificateValidationCallback. I had this same problem in the console app. I solved that by installing my root ca in my certificates store on the computer I'm running on. WAG perhaps the UWP app doesn't check the local computer certificates?

@baulig
Copy link

baulig commented Feb 20, 2019

O.K. no I don't think X509KeyStorageFlags are worth pursuing. They're for key stores which I'm not using.

We don't even support them yet, they're just ignored.

@baulig
Copy link

baulig commented Feb 20, 2019

A PFX file is an encrypted container that can hold one of more certificates and a private key. If you want to use a certificate for authentication, then you need both the certificate and its private key - this is what the X509Certificate2 constructors that are taking a password argument are for.

If you use the X509Certificate2(byte[]) constructor or X509Certificate.CreateFromCertFile(string) then the format will be assumed to be either PEM or DER - which is an unencrypted container that can hold one or more certificates. And while you could - in theory - add an unencrypted private key to a PEM / DER file, the backend won't recognize this as it is generally bad practice to store it in plain.

If you're using a custom CA, then your PFX should contain the CA certificate as well.

You can create a PFX file using the openssl pkcs12 tool.

On OS X, you can also add your private key to the system keystore (using the "Keychain Access" app on OS X) and use a PEM/DER file only containing the certificates without keys - though this is experimental and not very well tested.

@baulig
Copy link

baulig commented Feb 20, 2019

Might be handy if the showed me the equivalent constructor :-) X509Certificate is immutable on this platform. Use the equivalent constructor instead

{System.PlatformNotSupportedException: X509Certificate is immutable on this platform. Use the equivalent constructor instead.   at System.Security.Cryptography.X509Certificates.X509Certificate.Import(String fileName)   at System.Security.Cryptography.X509Certificates.X509Certificate2.Import(String fileName)   at MqttDataService.MqttDataService.Initialize()}

This has been done to make the behavior consistent with .NET Core, where X509Certificate is immutable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants