How to pass client certificate using AutoRest client - api

We are using AutoRest for generating client code based on API Swagger files.
I'm trying to pass client certificate to the API. But noticed that generated client code doesn't accept WebRequestHandler.
Generated code looks like below:
public MyTestApiV1(Uri baseUri, params DelegatingHandler[] handlers) : this(handlers)
{
if (baseUri == null)
{
throw new ArgumentNullException("baseUri");
}
this.BaseUri = baseUri;
}
I feel like I'm missing something here. Has anyone managed to send client certificate using AutoRest?
Tried this but webRequestHandler is always null:
var webRequestHandler = client.HttpMessageHandlers.First() as WebRequestHandler;
if (webRequestHandler != null)
{
var secretRetrieved = keyVault.GetSecretAsync("my-cert");
var pfxBytes = Convert.FromBase64String(secretRetrieved.Result);
// or recreate the certificate directly
var certificate = new X509Certificate2(pfxBytes);
webRequestHandler.ClientCertificates.Add(certificate);
}

You can use another overloaded constructor:
/// <summary>
/// Initializes ServiceClient using base HttpClientHandler and list of handlers.
/// </summary>
/// <param name="rootHandler">Base HttpClientHandler.</param>
/// <param name="handlers">List of handlers from top to bottom (outer handler is the first in the list)</param>
protected ServiceClient(HttpClientHandler rootHandler, params DelegatingHandler[] handlers)
ServiceClient is the base class for generated clients. Therefore, code might look like:
var secretRetrieved = keyVault.GetSecretAsync("my-cert");
var pfxBytes = Convert.FromBase64String(secretRetrieved.Result);
// or recreate the certificate directly
var certificate = new X509Certificate2(pfxBytes);
WebRequestHandler webRequestHandler = new WebRequestHandler();
webRequestHandler.ClientCertificates.Add(certificate);
var client = new MyTestApiV1(webRequestHandler);
client.BaseUri = baseUri;

.net Core version
Ivan R's answer led me on the right path but it's a little different for .net core (2.2 at this point in time) as WebRequestHandler is not available in core.
I had to use a pfx file and password in my case. GetNumberPassedIn isn't in the generic Petstore Swagger template but was what I was testing with.
Program.cs:
using System;
using System.Net.Http;
namespace SimpleApi2.Console
{
class Program
{
static void Main(string[] args)
{
var certificate = new CertInfo().GetCertFromPfx(Const.PfxPath, Const.PfxPassword);
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
var client = new HttpClient(handler);
var petStore = new SwaggerPetstore(client, true);
petStore.BaseUri = new Uri(Const.PublicUrl);
var result = petStore.GetNumberPassedIn(135, Const.ApiKey);
System.Console.WriteLine(result.ToString());
System.Console.ReadKey();
}
}
}
CertInfo.cs:
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security;
namespace SimpleApi2.Console
{
class CertInfo
{
internal static byte[] ReadFile(string fileName)
{
FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read);
int size = (int)f.Length;
byte[] data = new byte[size];
f.Read(data, 0, size);
f.Close();
return data;
}
public CertInfo() { }
public X509Certificate2 GetCertFromPfx(string pfxFilePath, string password)
{
try
{
byte[] rawData = ReadFile(pfxFilePath);
var passwordAsChars = password.ToCharArray();
var securePassword = new SecureString();
foreach (char c in password)
securePassword.AppendChar(c);
securePassword.MakeReadOnly();
X509Certificate2 x509 = new X509Certificate2(pfxFilePath, password,
X509KeyStorageFlags.UserKeySet);
WriteCertInfo(x509);
return x509;
}
catch (DirectoryNotFoundException)
{
System.Console.WriteLine("Error: The directory specified could not be found.");
throw;
}
catch (IOException)
{
System.Console.WriteLine("Error: A file in the directory could not be accessed.");
throw;
}
catch (NullReferenceException)
{
System.Console.WriteLine("File must be a .cer file. Program does not have access to that type of file.");
throw;
}
}
private static void WriteCertInfo(X509Certificate2 x509)
{
//Print to console information contained in the certificate.
System.Console.WriteLine("{0}Subject: {1}{0}", Environment.NewLine, x509.Subject);
System.Console.WriteLine("{0}Issuer: {1}{0}", Environment.NewLine, x509.Issuer);
System.Console.WriteLine("{0}Version: {1}{0}", Environment.NewLine, x509.Version);
System.Console.WriteLine("{0}Valid Date: {1}{0}", Environment.NewLine, x509.NotBefore);
System.Console.WriteLine("{0}Expiry Date: {1}{0}", Environment.NewLine, x509.NotAfter);
System.Console.WriteLine("{0}Thumbprint: {1}{0}", Environment.NewLine, x509.Thumbprint);
System.Console.WriteLine("{0}Serial Number: {1}{0}", Environment.NewLine, x509.SerialNumber);
System.Console.WriteLine("{0}Friendly Name: {1}{0}", Environment.NewLine, x509.PublicKey.Oid.FriendlyName);
System.Console.WriteLine("{0}Public Key Format: {1}{0}", Environment.NewLine, x509.PublicKey.EncodedKeyValue.Format(true));
System.Console.WriteLine("{0}Raw Data Length: {1}{0}", Environment.NewLine, x509.RawData.Length);
System.Console.WriteLine("{0}Certificate to string: {1}{0}", Environment.NewLine, x509.ToString(true));
}
}
}

Related

Posting MultipartFormDataContent request with HttpClient gets Error while copying content to a stream

I send HTTP request with the MultipartFormDataContent but I'm getting the following error when the file size is more than 2mb:
Error while copying content to a stream.
Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host..
An existing connection was forcibly closed by the remote host.
But exactly the same request with more than 5mb file can be sent with PostMan successfully.
This is my code:
using (var client = new HttpClient(GetHttpClientHandler(requestUrl)))
{
using (var content = new MultipartFormDataContent())
{
for (int i = 0; i < images.Count; i++)
{
var fileName = $"file {(i + 1)}.png";
ByteArrayContent bContent = new ByteArrayContent(images[i]);
content.Add(bContent, "file", fileName);
}
using (var response = await client.PostAsync(requestUrl, content)) //Exception occurs here
{
resp = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
}
}
}
So, if the problem about the server-side why it works with PostMan? If the problem is in my code, what is that then? I think I did everything by convention.
This is not the recommended way of using HttpClient!
First create custom client servis with the required upload method:
public class MyClientService : IMyClientService
{
private readonly HttpCLient _client;
public MyClientService(HttpClient client)
{
_client = client;
}
public async Task<bool> UploadFilesAsync(MultipartFormDataContent content, string requestUrl)
{
var response = await _client.PostAsync(requestUrl, content);
// ...
}
}
Then register the custom client service in startup:
services.AddHttpClient<IMyClientService, MyClientService>();
Then inject your service to the controller/page model:
private readonly IMyClientService_client;
public UploadPage(IMyClientService client)
{
_client = client;
}
And use it to upload files:
using (var content = new MultipartFormDataContent())
{
for (int i = 0; i < images.Count; i++)
{
var fileName = $"file {(i + 1)}.png";
ByteArrayContent bContent = new ByteArrayContent(images[i]);
content.Add(bContent, "file", fileName);
}
var success = await _client.UploadFilesAsync(content, requestUrl);
}

ASP.Net Core - EC2 to S3 file upload with Access Denied

I have developed a .NET Core 3.1 Web API which allows the users to upload their documents to S3 bucket. When I deploy the API to AWS ElasticBeansTalk EC2 instance and call the endpoint which uploads the file to S3, I get an error "Access Denied".
By the way, I have created IAM policy and role to give full access to S3 from my EC2 instance. I have also copied the .aws folder which contains credentials file onto the EC2 instance.
API Controller Action
public async Task<ApiResponse> UpdateProfilePic([FromBody]UploadProfilePicRequest model)
{
using (Stream stream = model.profilePicData.Base64StringToStream(out string header))
{
var tags = new List<KeyValuePair<string, string>>();
var metaData = new List<KeyValuePair<string, string>>();
metaData.Add(new KeyValuePair<string, string>("Content-Disposition", $"attachment; filename=\"{model.filename}\""));
if (_host.IsDevelopment())
{
tags.Add(new KeyValuePair<string, string>("public", "yes"));
}
await AmazonS3Uploader.UploadFileAsync(stream, "myDir/", model.fileId, tags, metaData);
}
}
The AmazonS3Helper class shown below:
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace UploderApp.Services
{
public static class AmazonS3Uploader
{
private static readonly RegionEndpoint bucketRegion = RegionEndpoint.APSouth1;
private static readonly IAmazonS3 s3Client = new AmazonS3Client(GetAwsCredentials(), bucketRegion);
private static readonly string S3Bucket = "abc-test";
private static AWSCredentials GetAwsCredentials()
{
var chain = new CredentialProfileStoreChain();
if (chain.TryGetAWSCredentials("MYPROFILE", out AWSCredentials awsCredentials))
{
return awsCredentials;
}
return null;
}
public static async Task UploadFileAsync(Stream fileStream, string virtualDirectory, string keyName)
{
try
{
using (var fileTransferUtility = new TransferUtility(s3Client))
{
//Upload data from a type of System.IO.Stream.
await fileTransferUtility.UploadAsync(fileStream, S3Bucket, virtualDirectory + keyName).ConfigureAwait(true);
}
}
catch (AmazonS3Exception e)
{
throw new Exception($"Error encountered on server. Message:'{e.Message}' when writing an object");
}
}
public static async Task UploadFileAsync(Stream stream, string virtualDirectory, string keyName, List<KeyValuePair<string, string>> tags = null, List<KeyValuePair<string, string>> metadata = null)
{
try
{
// Specify advanced settings.
var fileTransferUtilityRequest = new TransferUtilityUploadRequest
{
BucketName = S3Bucket,
InputStream = stream,
StorageClass = S3StorageClass.Standard,
Key = virtualDirectory + keyName
};
if (metadata != null)
{
foreach (var item in metadata)
{
fileTransferUtilityRequest.Metadata.Add(item.Key, item.Value);
}
}
if (tags != null)
{
fileTransferUtilityRequest.TagSet = new List<Tag>();
foreach (var tag in tags)
{
fileTransferUtilityRequest.TagSet.Add(new Tag { Key = tag.Key, Value = tag.Value });
}
}
using (var fileTransferUtility = new TransferUtility(s3Client))
{
await fileTransferUtility.UploadAsync(fileTransferUtilityRequest).ConfigureAwait(true);
}
}
catch (AmazonS3Exception e)
{
throw new Exception($"Error encountered on server. Message:'{e.Message}' when writing an object");
}
}
}
}
However, if I create a console application and use the above class without any modifications, it uploads the file from the same EC2 instance.
Code from the Main function of my Console Application.
public static void Main()
{
var file = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "/Screenshot.png";
try
{
var tags = new List<KeyValuePair<string, string>>();
var metaData = new List<KeyValuePair<string, string>>();
metaData.Add(new KeyValuePair<string, string>("Content-Disposition", $"attachment; filename=\"profile-pic.png\""));
using (var stream = new FileStream(file, FileMode.Open))
{
AmazonS3Uploader.UploadFileAsync(stream, "mydir/", "screenshot.png", tags, metaData).GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
This is very strange. Can anybody help me to understand the root cause, please?
Edit:1
Output of the aws s3 ls s3://abc-test is shown below
Edit:2
Uploading the EC2 folder to S3

Owin self host SSL connection reset

I got an issue when setting up HTTPs for a self host Owin console application. The browser always shows a connection reset error.
I've tried creating certificates manually according to this link http://chavli.com/how-to-configure-owin-self-hosted-website-with-ssl/ but I still get the connection reset issue on that port.
And I've checked the windows event log and there's no error messages.
The application will create X509 certificate by itself and run netsh command automatically.
Without Ssl, the application can display the web page correctly.
Can any one run my code below and see whether it can work on your computer?
Thanks in advance.
Need to add COM reference CertEnroll 1.0 Type Library to compile the code below (vs2015 already contains this COM reference in Type Libraries)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using CERTENROLLLib;
using Microsoft.Owin.Hosting;
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
namespace Owin.Startup
{
class Program
{
static void Main(string[] args)
{
int port = 8888;
string url = $"https://localhost:{port}";
var cert = GetCert("localhost", TimeSpan.FromDays(3650), "devpwd", AppDomain.CurrentDomain.BaseDirectory + "cert.dat");
ActivateCert(cert, port, GetAppId());
using (WebApp.Start<Startup>(url))
{
Console.WriteLine($"Hosted: {url}");
Console.ReadLine();
}
}
static private string GetAppId()
{
Assembly assembly = Assembly.GetExecutingAssembly();
//The following line (part of the original answer) is misleading.
//**Do not** use it unless you want to return the System.Reflection.Assembly type's GUID.
//Console.WriteLine(assembly.GetType().GUID.ToString());
// The following is the correct code.
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
var id = attribute.Value;
return id;
}
static public X509Certificate2 GetCert(string cn, TimeSpan expirationLength, string pwd = "", string filename = null)
{
// http://stackoverflow.com/questions/18339706/how-to-create-self-signed-certificate-programmatically-for-wcf-service
// http://stackoverflow.com/questions/21629395/http-listener-with-https-support-coded-in-c-sharp
// https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.storename(v=vs.110).aspx
// create DN for subject and issuer
var base64encoded = string.Empty;
if (filename != null && File.Exists(filename))
{
base64encoded = File.ReadAllText(filename);
}
else
{
base64encoded = CreateCertContent(cn, expirationLength, pwd);
if (filename != null)
{
File.WriteAllText(filename, base64encoded);
}
}
// instantiate the target class with the PKCS#12 data (and the empty password)
var rlt = new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), pwd,
// mark the private key as exportable (this is usually what you want to do)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
);
return rlt;
}
private static string CreateCertContent(string cn, TimeSpan expirationLength, string pwd)
{
string base64encoded = string.Empty;
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + cn, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG |
X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = cn; // Optional: add a friendly name
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, pwd); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
base64encoded = enroll.CreatePFX(pwd, // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
return base64encoded;
}
private static void ActivateCert(X509Certificate2 rlt, int port, string appId)
{
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
if (!store.Certificates.Contains(rlt))
{
store.Add(rlt);
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "netsh";
psi.Arguments = $"http delete sslcert ipport=0.0.0.0:{port}";
Process procDel = Process.Start(psi);
procDel.WaitForExit();
psi.Arguments = $"http add sslcert ipport=0.0.0.0:{port} certhash={rlt.Thumbprint} appid={{{appId}}}";
Process proc = Process.Start(psi);
proc.WaitForExit();
psi.Arguments = $"http delete sslcert ipport=[::]:{port}";
Process procDelV6 = Process.Start(psi);
procDelV6.WaitForExit();
psi.Arguments = $"http add sslcert ipport=[::]:{port} certhash={rlt.Thumbprint} appid={{{appId}}}";
Process procV6 = Process.Start(psi);
procV6.WaitForExit();
psi.Arguments = $"http add urlacl url=https://+:{port}/ user={Environment.UserDomainName}\\{Environment.UserName}";
Process procAcl = Process.Start(psi);
procAcl.WaitForExit();
}
store.Close();
}
}
public class Startup
{
private IAppBuilder app;
public void Configuration(IAppBuilder app)
{
#if DEBUG
app.UseErrorPage();
#endif
app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
{
Console.WriteLine("Begin Request");
foreach (var i in env.Keys)
{
Console.WriteLine($"{i}\t={(env[i] == null ? "null" : env[i].ToString())}\t#\t{(env[i] == null ? "null" : env[i].GetType().FullName)}");
}
if (next != null)
{
await next.Invoke(env);
}
else
{
Console.WriteLine("Process Complete");
}
Console.WriteLine("End Request");
})));
app.UseWelcomePage("/");
this.app = app;
}
}
}
You should not create certificate with the base64 content which loaded from the cert.dat. Try using cert.Export(X509ContentType.Pfx, pwd) and load it with new X509Certificate2(filename, pwd, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

SerializeObject and DeserializeObject

I use this following code but it gives error
Service1.svc.cs
public static byte[] SerializeObject<T>(T obj)
{
using (MemoryStream ms = new MemoryStream())
{
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms))
{
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
dcs.WriteObject(writer, obj); writer.Flush();
return ms.ToArray();
}
}
}
client code Windows phone
public static T DeserializeObject<T>(byte[] xml)
{
using (MemoryStream memoryStream = new MemoryStream(xml))
{
using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(memoryStream, XmlDictionaryReaderQuotas.Max))
{
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
return (T)dcs.ReadObject(reader);
}
}
}
I call this DeserializeObject from below code
void svc_Get_Conn(object send, GetConnCompletedEventArgs e)
{
CookieContainer con =DeserializeObject<CookieContainer>(e.Result);
}
This gives following error
Message = "Type 'System.Net.PathList' with data contract name 'PathList:http://schemas.datacontract.org/2004/07/System.Net' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using...
How to solve this?
CookieContainer can't be serializable. Check this workaround
cheers

JavaFX: File upload to REST service / servlet fails because of missing boundary

I'm trying to upload a file using JavaFX using the HttpRequest. For this purpose I have written the following function.
function uploadFile(inputFile : File) : Void {
// check file
if (inputFile == null or not(inputFile.exists()) or inputFile.isDirectory()) {
return;
}
def httpRequest : HttpRequest = HttpRequest {
location: urlConverter.encodeURL("{serverUrl}");
source: new FileInputStream(inputFile)
method: HttpRequest.POST
headers: [
HttpHeader {
name: HttpHeader.CONTENT_TYPE
value: "multipart/form-data"
}
]
}
httpRequest.start();
}
On the server side, I am trying to handle the incoming data using the Apache Commons FileUpload API using a Jersey REST service. The code used to do this is a simple copy of the FileUpload tutorial on the Apache homepage.
#Path("Upload")
public class UploadService {
public static final String RC_OK = "OK";
public static final String RC_ERROR = "ERROR";
#POST
#Produces("text/plain")
public String handleFileUpload(#Context HttpServletRequest request) {
if (!ServletFileUpload.isMultipartContent(request)) {
return RC_ERROR;
}
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = null;
try {
items = upload.parseRequest(request);
}
catch (FileUploadException e) {
e.printStackTrace();
return RC_ERROR;
}
...
}
}
However, I get a exception at items = upload.parseRequest(request);:
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I guess I have to add a manual boundary info to the InputStream. Is there any easy solution to do this? Or are there even other solutions?
Have you tried just using the InputStream from HttpServletRequest like so
InputStream is = httpRequest.getInputStream();
BufferedInputStream in = new BufferedInputStream(is);
//Write out bytes
out.close();
is.close();