xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.45k stars 511 forks source link

iOS RSA Key Pair Generated and Certificate send to Server, but Server cannot recognize it. (X509Certificate2) #11781

Closed filmar25 closed 3 years ago

filmar25 commented 3 years ago

Hello,

I would like to submit this case.

My goal is to generate RSA Key Pair on iOS and after send the certificate (X509) to our WCF Service to be stored. We use it to create a token for the device that has generated the certificate. This securise our communication with this device.

Steps to Reproduce

First, I generate a key pair on iOS :

    public bool CreatePrivateKey()
    {
        try
        {
            SecKey.GenerateKeyPair(CreateRsaParams(), out privateKey, out publicKey);
            return true;
        }
        catch (System.Exception ex)
        {
            return false;
        }
    }

    private NSDictionary CreateRsaParams()
    {
        IList<object> keys = new List<object>();
        IList<object> values = new List<object>();

        //creating the private key params
        keys.Add(IosConstants.Instance.KSecAttrApplicationTag);
        keys.Add(IosConstants.Instance.KSecAttrIsPermanent);
        keys.Add(IosConstants.Instance.KSecAttrAccessible);

        values.Add(NSData.FromString(_keyName, NSStringEncoding.UTF8));
        values.Add(NSNumber.FromBoolean(true));
        values.Add(IosConstants.Instance.KSecAccessibleWhenUnlocked);

        NSDictionary privateKeyAttributes = NSDictionary.FromObjectsAndKeys(values.ToArray(), keys.ToArray());

        keys.Clear();
        values.Clear();

        //creating the keychain entry params
        //no need for public key params, as it will be created from the private key once it is needed
        keys.Add(IosConstants.Instance.KSecAttrKeyType);
        keys.Add(IosConstants.Instance.KSecAttrKeySize);
        keys.Add(IosConstants.Instance.KSecPrivateKeyAttrs);

        values.Add(IosConstants.Instance.KSecAttrKeyTypeRSA);
        values.Add(NSNumber.FromInt32(this.KeySize));
        values.Add(privateKeyAttributes);

        return NSDictionary.FromObjectsAndKeys(values.ToArray(), keys.ToArray());
    }

Second, I extract the certificate. At this stage, I send the string directly to the WCF Service to be stored :

    public string GetDevicePublicCertificate()
    {
        try
        {
            var cert = this.publicKey.GetExternalRepresentation();
            var publicKeyBytes = new byte[cert.Length];
            System.Runtime.InteropServices.Marshal.Copy(cert.Bytes, publicKeyBytes, 0, Convert.ToInt32(cert.Length));
            return Convert.ToBase64String(publicKeyBytes, Base64FormattingOptions.InsertLineBreaks);
        }
        catch (Exception ex)
        {
            return null;
        }
    }

Third, on the server, I want to use it :

    public void SetDistantCertificate(string cer)
    {
        try
        {
            var bytes = Convert.FromBase64String(cer);
            X509Certificate2 cert = new X509Certificate2(bytes);    <========= This line crash : Cannot find the requested object.
            this.rsaDistant = cert.GetRSAPublicKey();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

Expected Behavior

I already did the job with Xamarin.Android and Windows Application and both are working well. I can create RSA Key Pair on the device and send the certificate to the WCF Service to be stored and after I can use it without problem. I use the device certificate to encrypt the token generated on the server.

Actual Behavior

I spend long time checking posts and saw many examples in swift, objectif c. It seems that iOS didn't export to X.509 format. I also saw that Microsoft has made classes like SecCertificate and SecCertificate2 in Xamarin.iOS that can export X.509 certificate, but when I use it, debugger freeze at the export of X509Certificate.

Environment

Microsoft Visual Studio Professional 2019
Version 16.9.5
VisualStudio.16.Release/16.9.5+31229.75
Microsoft .NET Framework
Version 4.8.04084

Installed Version: Professional

Visual C++ 2019   00435-20310-99980-AA168
Microsoft Visual C++ 2019

ASP.NET and Web Tools 2019   16.9.693.2781
ASP.NET and Web Tools 2019

ASP.NET Core Razor Language Services   16.1.0.2112521+5741df381174d72f08e3632bb99f52e8635b6a1a
Provides languages services for ASP.NET Core Razor.

ASP.NET Web Frameworks and Tools 2019   16.9.693.2781
For additional information, visit https://www.asp.net/

Azure App Service Tools v3.0.0   16.9.693.2781
Azure App Service Tools v3.0.0

Azure Functions and Web Jobs Tools   16.9.693.2781
Azure Functions and Web Jobs Tools

C# Tools   3.9.0-6.21160.10+59eedc33d35754759994155ea2f4e1012a9951e3
C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

CodeRush for Roslyn   21.1.3.0
DevExpress CodeRush for Roslyn package.

CodeRush for Roslyn Tool Windows   21.1.3.0
DevExpress CodeRush for Roslyn Tool Windows package.

Common Azure Tools   1.10
Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.

ConvertToDevExtremeCommand Extension   1.0
ConvertToDevExtremeCommand Visual Studio Extension Detailed Info

CreateLayoutWizard   1.0
Create layout wizard.

DevExpress Reporting Extension   1.3
A Visual Studio extension that invokes the Report Designer editor for report definition VSREPX files.

DevExpress Reporting Tools Extension   1.0
Extends Visual Studio with tools required for the Report Designer editor.

DevExpress.DeploymentTool   1.0
A useful tool for deploying DevExpress assemblies.

DevExpress.Win.LayoutAssistant Extension   1.0
DevExpress.Win.LayoutAssistant Visual Studio Extension Detailed Info

Extensibility Message Bus   1.2.6 (master@34d6af2)
Provides common messaging-based MEF services for loosely coupled Visual Studio extension components communication and integration.

IntelliCode Extension   1.0
IntelliCode Visual Studio Extension Detailed Info

Microsoft Azure Tools   2.9
Microsoft Azure Tools for Microsoft Visual Studio 2019 - v2.9.40423.1

Microsoft Continuous Delivery Tools for Visual Studio   0.4
Simplifying the configuration of Azure DevOps pipelines from within the Visual Studio IDE.

Microsoft JVM Debugger   1.0
Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines

Microsoft Library Manager   2.1.113+g422d40002e.RR
Install client-side libraries easily to any web project

Microsoft MI-Based Debugger   1.0
Provides support for connecting Visual Studio to MI compatible debuggers

Microsoft Visual C++ Wizards   1.0
Microsoft Visual C++ Wizards

Microsoft Visual Studio Tools for Containers   1.1
Develop, run, validate your ASP.NET Core applications in the target environment. F5 your application directly into a container with debugging, or CTRL + F5 to edit & refresh your app without having to rebuild the container.

Microsoft Visual Studio VC Package   1.0
Microsoft Visual Studio VC Package

Mono Debugging for Visual Studio   16.9.7 (df23ba6)
Support for debugging Mono processes with Visual Studio.

Node.js Tools   1.5.21130.2 Commit Hash:dacc0e71a5e060cdcab466a301cacea359c1b6da
Adds support for developing and debugging Node.js apps in Visual Studio

NuGet Package Manager   5.9.0
NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/

Productivity Power Tools 2017/2019   16.0
Installs the individual extensions of Productivity Power Tools 2017/2019

ProjectServicesPackage Extension   1.0
ProjectServicesPackage Visual Studio Extension Detailed Info

ResXManager   ResXManager
Manage localization of all ResX-Based resources in one place. Shows all resources of a solution and let's you edit the strings and their localizations in a well-arranged data grid.

SQL Server Data Tools   16.0.62103.10080
Microsoft SQL Server Data Tools

Test Adapter for Boost.Test   1.0
Enables Visual Studio's testing tools with unit tests written for Boost.Test.  The use terms and Third Party Notices are available in the extension installation directory.

Test Adapter for Google Test   1.0
Enables Visual Studio's testing tools with unit tests written for Google Test.  The use terms and Third Party Notices are available in the extension installation directory.

TypeScript Tools   16.0.30201.2001
TypeScript Tools for Microsoft Visual Studio

Visual Basic Tools   3.9.0-6.21160.10+59eedc33d35754759994155ea2f4e1012a9951e3
Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Visual F# Tools   16.9.0-beta.21102.9+7ce7132f1459095e635194d09d6f73265352029a
Microsoft Visual F# Tools

Visual Studio Code Debug Adapter Host Package   1.0
Interop layer for hosting Visual Studio Code debug adapters in Visual Studio

Visual Studio Container Tools Extensions   1.0
View, manage, and diagnose containers within Visual Studio.

Visual Studio Tools for CMake   1.0
Visual Studio Tools for CMake

Visual Studio Tools for Containers   1.0
Visual Studio Tools for Containers

VisualStudio.DeviceLog   1.0
Information about my package

VisualStudio.Mac   1.0
Mac Extension for Visual Studio

Xamarin   16.9.000.292 (d16-9@26a958f)
Visual Studio extension to enable development for Xamarin.iOS and Xamarin.Android.

Xamarin Designer   16.9.0.318 (remotes/origin/7b35adcdd1b204bbdcb279e0f029d151a20a4bf3@7b35adcdd)
Visual Studio extension to enable Xamarin Designer tools in Visual Studio.

Xamarin Templates   16.9.72 (426ebf6)
Templates for building iOS, Android, and Windows apps with Xamarin and Xamarin.Forms.

Xamarin.Android SDK   11.2.2.1 (d16-9/877f572)
Xamarin.Android Reference Assemblies and MSBuild support.
    Mono: 5e9cb6d
    Java.Interop: xamarin/java.interop/d16-9@54f8c24
    ProGuard: Guardsquare/proguard/v7.0.1@912d149
    SQLite: xamarin/sqlite/3.34.1@daff8f4
    Xamarin.Android Tools: xamarin/xamarin-android-tools/d16-9@d210f11

Xamarin.iOS and Xamarin.Mac SDK   14.16.0.5 (ab40b131d)
Xamarin.iOS and Xamarin.Mac Reference Assemblies and MSBuild support.
spouliot commented 3 years ago

I already did the job with Xamarin.Android and Windows Application and both are working well.

I do not know which API/service/tool you used in the above case. However what you're doing here is incorrect.

var cert = this.publicKey.GetExternalRepresentation();

This does not return an X.509 certificate. This is why your WCF service won't accept it.

Long Answer

The usual way to create a .509 certificate is to:

Neither the .net framework (I have not checked net5+), nor Apple APIs, provide a certificate authority or any API to create a certificate.

Since it's not generated (locally) and you did not import a certificate (from a CA) then you cannot export it (as it does not exists).

filmar25 commented 3 years ago

Hello spouliot,

Thank you for taking your time to answer me.

With Xamarin.Android, I proceed like this :

First I generate the Key Pair and I can easily extract the certificate without create a certificate request. I only use the Xamarin.Android sdk :

    public void InitDeviceCertificate(bool deleteCertIfExists)
    {
        try
        {
            var ks = KeyStore.GetInstance("AndroidKeyStore");
            ks.Load(null);

            if (ks.ContainsAlias(RSAStrings.LocalCertificateName) && deleteCertIfExists)
            {
                ks.DeleteEntry(RSAStrings.LocalCertificateName);
            }

            if (ks.ContainsAlias(RSAStrings.LocalCertificateName) == false)
            {
                // // // // // // // // // // // // // // // // // // 
                // Generate new certificate on the Android Device //
                // // // // // // // // // // // // // // // // //

                var kpg = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, ks.Provider);

                var cal = Calendar.GetInstance(Java.Util.TimeZone.Default);
                cal.Set(DateTime.Now.Year + 50, 1, 1);
                var expiration = new Date(cal.TimeInMillis);

                kpg.Initialize(
                    new KeyGenParameterSpec.Builder
                        (RSAStrings.LocalCertificateName, KeyStorePurpose.Sign | KeyStorePurpose.Verify | KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
                        .SetCertificateSubject(new Javax.Security.Auth.X500.X500Principal($"CN={RSAStrings.LocalCertificateName}"))
                        .SetDigests(KeyProperties.DigestSha256)
                        .SetEncryptionPaddings(KeyProperties.EncryptionPaddingRsaPkcs1)
                        .SetSignaturePaddings(KeyProperties.SignaturePaddingRsaPss)
                        .SetCertificateNotAfter(expiration)
                        .SetRandomizedEncryptionRequired(false)
                        .SetBlockModes(KeyProperties.BlockModeEcb)
                        .SetUserPresenceRequired(false)
                        .SetUnlockedDeviceRequired(false)
                        .SetUserConfirmationRequired(false)
                        .SetKeySize(2048).Build()
                );

                var kp = kpg.GenerateKeyPair();
                this.privateKey = kp.Private;
                this.publicKey = kp.Public;

                this.certificate = ks.GetCertificate(RSAStrings.LocalCertificateName);
                this.key = ks.GetKey(RSAStrings.LocalCertificateName, null);
            }
            else
            {
                this.certificate = ks.GetCertificate(RSAStrings.LocalCertificateName);
                this.publicKey = this.certificate.PublicKey;

                KeyStore.IEntry entry = ks.GetEntry(RSAStrings.LocalCertificateName, null);
                this.privateKey = ((KeyStore.PrivateKeyEntry)entry).PrivateKey;

                this.key = ks.GetKey(RSAStrings.LocalCertificateName, null);
            }
        }
        catch (Exception ex)
        {
            var i = 0;
        }
    }

Second, I extract the certificate :

    public string GetDevicePublicCertificate()
    {
        return Convert.ToBase64String(this.certificate.GetEncoded(), Base64FormattingOptions.InsertLineBreaks);
    }

Third, I use it on the server :

public void SetDistantCertificate(string cer)
{
    try
    {
        var bytes = Convert.FromBase64String(cer);
        X509Certificate2 cert = new X509Certificate2(bytes);    <========= This line crash on iOS : Cannot find the requested object.
        this.rsaDistant = cert.GetRSAPublicKey();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

According to the Apple documentation, it seems that the publicKey is exportable and usable externally like Android is doing : https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_as_data?language=objc

On Xamarin.macios, GetExternalRepresentation using SecKeyCopyExternalRepresentation like the Apple documentation explain :

https://github.com/xamarin/xamarin-macios/blob/2b7dc07d20fc06344d7f164af8934f5e755572c2/src/Security/Certificate.cs#L847

I also found this post of 2011 about iOS : https://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/

This blog is base on https://blog.wingsofhermes.org/?p=75

The Apple documentation said that SecKeyCopyExternalRepresentation give a PKCS#1 data. But like I saw in the post we have to make manipulation of the data before using it outside of iOS.

I will investigate those post to see what I can do with the publicKey to add bytes to the result of GetExternalRepresentation.

I am newbie with RSA, so I miss a lot of things.

Thank you !

spouliot commented 3 years ago

According to the Apple documentation, it seems that the publicKey is exportable and usable externally

Yes, but a public key is not a X.509 certificate. A X.509 certificate includes the public key. A public key does not include a certificate.

If you need the later (X.509) then someone, somewhere needs to create it (and that won't come from iOS).

I am newbie with RSA, so I miss a lot of things.

It's X.509, not RSA, related. Sadly that does not make it easier