public async Task <(X509Certificate2 identityCert, X509Certificate2[] certChain)> GetIdentityCertificateAsync(TimeSpan validity, CancellationToken token = default) { var request = new IdentityCertificateRequest { Expiration = DateTime.UtcNow.Add(validity), }; string requestString = JsonConvert.SerializeObject(request, Formatting.None, this.jsonSettings); using (var content = new StringContent(requestString, Encoding.UTF8, "application/json")) using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, this.postIdentityCertificateRequestUri) { Content = content }) using (HttpResponseMessage httpResponse = await this.httpClient.SendAsync(httpRequest, token)) { string responsePayload = await httpResponse.Content.ReadAsStringAsync(); if (httpResponse.StatusCode == HttpStatusCode.Created) { CertificateResponse cr = JsonConvert.DeserializeObject <CertificateResponse>(responsePayload, this.jsonSettings); return(this.CreateX509Certificates(cr)); } throw new InvalidOperationException($"Failed to retrieve identity certificate from IoTEdge security daemon. StatusCode={httpResponse.StatusCode} ReasonPhrase='{httpResponse.ReasonPhrase}' ResponsePayload='{responsePayload}' Request={requestString} This={this}"); } }
private (X509Certificate2 primaryCert, X509Certificate2[] certChain) CreateX509Certificates(CertificateResponse cr, [CallerMemberName] string callerMemberName = default) { Validate.ArgumentNotNullOrEmpty(cr.Certificate, nameof(cr.Certificate)); Validate.ArgumentNotNull(cr.Expiration, nameof(cr.Expiration)); Validate.ArgumentNotNull(cr.PrivateKey, nameof(cr.PrivateKey)); Validate.ArgumentNotNull(cr.PrivateKey.Type, nameof(cr.PrivateKey.Type)); Validate.ArgumentNotNull(cr.PrivateKey.Bytes, nameof(cr.PrivateKey.Bytes)); string[] rawCerts = ParseCertificateResponse(cr.Certificate); if (rawCerts.Length == 0 || string.IsNullOrWhiteSpace(rawCerts[0])) { throw new InvalidOperationException($"Failed to retrieve certificate from IoTEdge Security daemon for {callerMemberName}. Reason: Security daemon returned an empty response."); } string primaryCert = rawCerts[0]; X509Certificate2[] certChain = ConvertToX509(rawCerts.Skip(1)); RsaPrivateCrtKeyParameters keyParams = null; var chainCertEntries = new List <X509CertificateEntry>(); Pkcs12Store store = new Pkcs12StoreBuilder().Build(); // note: the seperator between the certificate and private key is added for safety to delineate the cert and key boundary using (var sr = new StringReader(primaryCert + "\r\n" + cr.PrivateKey.Bytes)) { var pemReader = new PemReader(sr); object certObject; while ((certObject = pemReader.ReadObject()) != null) { if (certObject is Org.BouncyCastle.X509.X509Certificate x509Cert) { chainCertEntries.Add(new X509CertificateEntry(x509Cert)); } // when processing certificates generated via openssl certObject type is of AsymmetricCipherKeyPair if (certObject is AsymmetricCipherKeyPair ackp) { certObject = ackp.Private; } if (certObject is RsaPrivateCrtKeyParameters rpckp) { keyParams = rpckp; } } } if (keyParams == null) { throw new InvalidOperationException($"Private key was not found for {callerMemberName}"); } store.SetKeyEntry(this.ModuleId, new AsymmetricKeyEntry(keyParams), chainCertEntries.ToArray()); using (var ms = new MemoryStream()) { store.Save(ms, Array.Empty <char>(), new SecureRandom()); var x509PrimaryCert = new X509Certificate2(ms.ToArray()); return(x509PrimaryCert, certChain); } }