0

Is there a way to programmatically upload an x509 certificate created in Visual Studios into Azure application manifest?

I followed this post to create the x509 certificate:

public static X509Certificate2 GenerateSelfSignedCertificate(string subjectName, string issuerName, AsymmetricKeyParameter issuerPrivKey)
{
    const int keyStrength = 2048;

    //generate random numbers
    CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
    SecureRandom random = new SecureRandom(randomGenerator);
    ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);

    //the certificate generator
    X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
    certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, true, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));

    //serial number
    BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random );
    certificateGenerator.SetSerialNumber(serialNumber);

    // Issuer and Subject Name
    X509Name subjectDN = new X509Name("CN="+ subjectName);
    X509Name issuerDN = new X509Name("CN="+issuerName);
    certificateGenerator.SetIssuerDN(issuerDN);
    certificateGenerator.SetSubjectDN(subjectDN);

    //valid For
    DateTime notBefore = DateTime.Now;
    DateTime notAfter = notBefore.AddYears(2);
    certificateGenerator.SetNotBefore(notBefore);
    certificateGenerator.SetNotAfter(notAfter);

    //Subject Public Key
    AsymmetricCipherKeyPair subjectKeyPair;
    var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
    var keyPairGenerator = new RsaKeyPairGenerator();
    keyPairGenerator.Init(keyGenerationParameters);
    subjectKeyPair = keyPairGenerator.GenerateKeyPair();

    certificateGenerator.SetPublicKey(subjectKeyPair.Public);

    //selfSign certificate
    Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
    var dotNetPrivateKey = ToDotNetKey((RsaPrivateCrtKeyParameters) subjectKeyPair.Private);

    //merge into X509Certificate2
    X509Certificate2 x509 = new X509Certificate2(DotNetUtilities.ToX509Certificate(certificate));
    x509.PrivateKey = dotNetPrivateKey;
    x509.FriendlyName = subjectName;

    return x509;
}


public static X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, out AsymmetricKeyParameter CaPrivateKey)
{
    const int keyStrength = 2048;

    //generate Random Numbers
    CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
    SecureRandom random = new SecureRandom(randomGenerator);

    //The Certificate Generator
    X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();

    //Serial Number
    BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
    certificateGenerator.SetSerialNumber(serialNumber);

    //Issuer and Subject Name
    X509Name subjectDN = new X509Name("CN="+subjectName);
    X509Name issuerDN = subjectDN;
    certificateGenerator.SetIssuerDN(issuerDN);
    certificateGenerator.SetSubjectDN(subjectDN);

    //valid For
    DateTime notBefore = DateTime.Now;
    DateTime notAfter = notBefore.AddYears(2);

    certificateGenerator.SetNotBefore(notBefore);
    certificateGenerator.SetNotAfter(notAfter);

    //subject Public Key
    AsymmetricCipherKeyPair subjectKeyPair;
    KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
    RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
    keyPairGenerator.Init(keyGenerationParameters);
    subjectKeyPair = keyPairGenerator.GenerateKeyPair();

    certificateGenerator.SetPublicKey(subjectKeyPair.Public);

    //generating the certificate
    AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;
    ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerKeyPair.Private, random);

    //selfSign Certificate
    Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);

    X509Certificate2 x509 = new X509Certificate2(certificate.GetEncoded());
    x509.FriendlyName = subjectName;
    CaPrivateKey = issuerKeyPair.Private;

    return x509;
}

public static AsymmetricAlgorithm ToDotNetKey(RsaPrivateCrtKeyParameters privateKey)
{
    var cspParams = new CspParameters()
    {
        KeyContainerName = Guid.NewGuid().ToString(),
        KeyNumber = (int)KeyNumber.Exchange,
        Flags = CspProviderFlags.UseMachineKeyStore
    };

    var rsaProvider = new RSACryptoServiceProvider(cspParams);
    var parameters = new RSAParameters()
    {
        Modulus = privateKey.Modulus.ToByteArrayUnsigned(),
        P = privateKey.P.ToByteArrayUnsigned(),
        Q = privateKey.Q.ToByteArrayUnsigned(),
        DP = privateKey.DP.ToByteArrayUnsigned(),
        DQ = privateKey.DQ.ToByteArrayUnsigned(),
        InverseQ = privateKey.QInv.ToByteArrayUnsigned(),
        D = privateKey.Exponent.ToByteArrayUnsigned(),
        Exponent = privateKey.PublicExponent.ToByteArrayUnsigned()
    };

    rsaProvider.ImportParameters(parameters);

    return rsaProvider;
}

and add it X509Store like so:

public static bool addCertToStore(System.Security.Cryptography.X509Certificates.X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
{
    bool bRet = false;

    try
    {
        X509Store store = new X509Store(st, sl);
        store.Open(OpenFlags.ReadWrite);
        store.Add(cert);

        store.Close();
    }
    catch
    {

    }

    return bRet;
}

Basically, I want to upload the cert that I create in Visual Studio to the application manifest in the Azure portal or Microsoft registration portal in order to get a stronger access token to be used to write events to Outlook calendar. I have googled around for two days now and still no luck... is there a documentation I'm missing?

I need to use x509 certificate over the appSecret generated when making a new application in Microsoft registration portal.

Can anyone point me in the right direction?

Kami
  • 19,134
  • 4
  • 51
  • 63
Ako
  • 93
  • 2
  • 13

1 Answers1

2

Is there a way to programmatically upload an x509 certificate created in Visual Studios into Azure application manifest?

Yes, we could update the Azure application mainfest with Microsoft.Azure.ActiveDirectory.GraphClient.

I did a demo for that. The following is detail steps, you could refer to:

If we want to update the mainfest keyCredential we need DELEGATED PERMISSIONS

1.Registry an azure AD native application and grant [Access the directory as the signed-in user] permission.

enter image description here

2.Create a console application add the following code in the Program.cs file

 private static async Task<string> GetAppTokenAsync(string graphResourceId, string tenantId, string clientId, string userId)
        {

            string aadInstance = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
            IPlatformParameters parameters = new PlatformParameters(PromptBehavior.SelectAccount);
            AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance, false);
            var authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientId, new Uri("http://localhost"), parameters, new UserIdentifier(userId, UserIdentifierType.UniqueId));
            return authenticationResult.AccessToken;
        }

 var graphResourceId = "https://graph.windows.net";
 var tenantId = "tenantId";
 var clientId = "clientId";
 var userId= "313e5ee2-b28exx-xxxx"; Then login user
 var servicePointUri = new Uri(graphResourceId); 
 var serviceRoot = new Uri(servicePointUri, tenantId);
 var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetAppTokenAsync(graphResourceId, tenantId, clientId, userName));
 var cert = new X509Certificate();
 cert.Import(@"D:\Tom\Documents\tom.cer");// the path fo cert file
 var expirationDate  = DateTime.Parse(cert.GetExpirationDateString()).ToUniversalTime();
 var startDate = DateTime.Parse(cert.GetEffectiveDateString()).ToUniversalTime();
 var binCert =cert.GetRawCertData();
 var keyCredential = new KeyCredential
      {
                CustomKeyIdentifier = cert.GetCertHash(),
                EndDate = expirationDate,
                KeyId = Guid.NewGuid(),
                StartDate = startDate,
                Type = "AsymmetricX509Cert",
                Usage = "Verify",
                Value = binCert

        };

   var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
   application.KeyCredentials.Add(keyCredential);
   application.UpdateAsync().Wait();

Packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Azure.ActiveDirectory.GraphClient" version="2.1.1" targetFramework="net471" />
  <package id="Microsoft.Data.Edm" version="5.6.4" targetFramework="net471" />
  <package id="Microsoft.Data.OData" version="5.6.4" targetFramework="net471" />
  <package id="Microsoft.Data.Services.Client" version="5.6.4" targetFramework="net471" />
  <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="3.19.8" targetFramework="net471" />
  <package id="System.Spatial" version="5.6.4" targetFramework="net471" />
</packages>
Tom Sun - MSFT
  • 24,161
  • 3
  • 30
  • 47
  • Thanks! I was gonna scratch this approach since i didn't find any good documentation :) – Ako Jul 05 '18 at 11:22
  • Is this only possible with Delegated Permissions? I want the application to do this once for every new tenant. I don't want to prompt users to log in instead the application does this in the background – Ako Jul 05 '18 at 11:29
  • Yes, based on my test it is only possible with Delegated Permissions. `I don't want to prompt users to log in instead the application does this in the background` If I have free time I will look into that. Or you could post a new SO thread to get more help from other communities. – Tom Sun - MSFT Jul 05 '18 at 12:12
  • 1
    worst case I will do it with Delegated Permissions and make up a one time signup experience and in that process setup the "`keyCredential`". Thank you for the answer, as it is right now a tenant admin is required in my application to grant application permissions and I use "grant_type" "client_credentials" to fetch the accesstoken but I want to make it more secure and use "grant_type" "client_assertion" x509 certificate explained [here](https://learn.microsoft.com/en-us/azure/architecture/multitenant-identity/client-assertion). Thanks Again! – Ako Jul 05 '18 at 15:11
  • Could you post a link to your demo that you mentioned above – Ako Jul 09 '18 at 07:40
  • @Ako `post a link to your demo`, What does it mean? – Tom Sun - MSFT Jul 09 '18 at 07:44
  • My bad when I read "I did a demo for that" I thought you did a demo in a blog or something didn't know that you meant the answer that you posted :), I'm gonna go ahead and try to upload the certificate becouse it is more secure than the client secret that is generated :) – Ako Jul 09 '18 at 07:49
  • one more question, did you create the certificate in a command prompt? – Ako Jul 09 '18 at 07:51
  • I don't create the cert with your mentioned code. I create with command `makecert -sky exchange -r -n "CN=[CertificateName]" -pe -a sha1 -len 2048 -ss My "[CertificateName].cer`. You also could refer to another [SO thread](https://stackoverflow.com/questions/40231598/azure-website-scaling/40235731#40235731) to get more information about how to create a cert. – Tom Sun - MSFT Jul 09 '18 at 07:53
  • Can I use the Token acquired from [Get access without a user](https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_service) to upload the cert file? – Ako Jul 09 '18 at 10:15
  • tried this with application permissions and it gets stuck at `var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;`, I aquire the token a bit differently than you `var clientCredential = new ClientCredential(clientId, clientSecret); AuthenticationContext authenticationContextt = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}/oauth2/token"); AuthenticationResult result = await authenticationContextt.AcquireTokenAsync(graphResourceId, clientCredential);` – Ako Jul 09 '18 at 14:39
  • 1
    @Ako Based on my test, we need to use `DELEGATED PERMISSIONS` to update the keyCredential. That means we need to use a user to get the access token. – Tom Sun - MSFT Jul 10 '18 at 00:30
  • Ahh, that's Good to know :) I am New to this whole concept and misunderstood you, I thought that I needed an access token acquired from users logging in. Thanks for clearing that up :) – Ako Jul 10 '18 at 08:15
  • Tested your code out in a new application, works great however *.Result() and *.wait() gave me deadlocks, replased with await and also this defeats the purpose of my app being a non-interactive background application, I think I will move on to the other parts of the application and maybe come back to this later, Thanks for all the help!! – Ako Jul 10 '18 at 13:48