Teams Outgoing WebHook HMAC problem not matching - c#

I created an outgoing Teams webhook.
The callback URL points to a controller on my API, and I would like to use the HMAC provided by the webhook in the request header.
However, when I compute the HMAC with the secret key, I don't obtain the same key as the one in the header.
I tried this code :
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
if (!this.Request.Headers.TryGetValue("Authorization", out var headerValue))
{
return AuthenticateResult.Fail("Authorization header not found.");
}
var sentKey = headerValue.ToString().Replace("HMAC ", null);
string requestBody = null;
using (var reader = new StreamReader(this.Request.Body, Encoding.UTF8))
{
requestBody = await reader.ReadToEndAsync();
}
if (string.IsNullOrWhiteSpace(requestBody))
{
return AuthenticateResult.Fail("No content to authenticate.");
}
var secretKeyBytes = Encoding.UTF8.GetBytes(this.Options.SecretKey);
using (var hmac = new HMACSHA256(secretKeyBytes))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(requestBody));
var expectedSignature = WebEncoders.Base64UrlEncode(hash);
if (!string.Equals(sentKey, expectedSignature, StringComparison.Ordinal))
{
return AuthenticateResult.Fail("Invalid HMAC signature.");
}
}
var claimsIdentity = new ClaimsIdentity();
var ticket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
{
return AuthenticateResult.Fail($"{ex.HResult}, {ex.Message}");
}
}

Related

How to read object value from returned API response?

I have a web API that will validate a login from a client (console) and I want to retrieve the object (content) from the response returned from the console. Here is the snippet of the API code:
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] Login login)
{
if (ModelState.IsValid)
{
var user = await this.userManager.FindByNameAsync(login.Username);
if (user != null)
{
var passwordCheck = await this.signInManager.CheckPasswordSignInAsync(user, login.Password, false);
if (passwordCheck.Succeeded)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.config["Tokens:Key"]));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
this.config["Tokens:Issuer"],
this.config["Tokens:Audience"],
claims,
expires: DateTime.UtcNow.AddHours(3),
signingCredentials: credentials
);
return Ok(new
{
Token = new JwtSecurityTokenHandler().WriteToken(token),
Expiration = token.ValidTo
});
}
else
{
return Unauthorized("Wrong password or email!");
}
}
else
{
return Unauthorized("Must not empty");
}
}
return BadRequest();
}
If I tried to test the call from Postman, it will get what I expected:
However, I don't know how to get the same result from the console app. Here is the code:
static async Task<JwtToken> Login()
{
Login login = new();
JwtToken token = null;
Console.Write("Username: ");
login.Username = Console.ReadLine();
Console.Write("Password: ");
login.Password = Console.ReadLine();
HttpResponseMessage response = await client.PostAsJsonAsync("account/login", login);
if (response.IsSuccessStatusCode)
{
token = await response.Content.ReadAsAsync<JwtToken>();
Console.WriteLine("Token: {0}\nValid to: {1}",token.Token,token.Expiration);
return token;
}
else
{
Console.WriteLine(response.StatusCode.ToString());
return token;
}
}
And instead of the object, I will just get "BadRequest" or "Unauthorized". Thanks in advance if you can help me.
As the response's content is a string, you can achieve by reading the response.Content as string:
var errorMessage = await response.Content.ReadAsAsync<string>();
Place it in else statement.
else
{
var errorMessage = await response.Content.ReadAsAsync<string>();
Console.WriteLine(response.StatusCode.ToString());
return token;
}

Unable to send large attachment using graph api

I am trying to add a large attachment to an email using Microsoft Graph.
Steps:
Get Token:
public static async Task<GraphServiceClient> GetAuthenticatedClientForApp(IConfidentialClientApplication app)
{
GraphServiceClient graphClient = null;
// Create Microsoft Graph client.
try
{
var token = await GetTokenForAppAsync(app);
graphClient = new GraphServiceClient(
"https://graph.microsoft.com/beta",
new DelegateAuthenticationProvider(async(requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("bearer", token);
}));
return graphClient;
}
catch (Exception ex)
{
Logger.Error("Could not create a graph client: " + ex.Message);
}
return graphClient;
}
/// <summary>
/// Get Token for App.
/// </summary>
/// <returns>Token for app.</returns>
public static async Task<string> GetTokenForAppAsync(IConfidentialClientApplication app)
{
AuthenticationResult authResult;
authResult = await app
.AcquireTokenForClient(new string[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync(System.Threading.CancellationToken.None);
return authResult.AccessToken;
}
Create Draft:
Message draft = await client
.Users[emailDTO.FromEmail]
.Messages
.Request()
.AddAsync(msg);
Attach file:
if (emailDTO.FileAttachments != null && emailDTO.FileAttachments.Count() > 0)
{
foreach (EmailAttachment emailAttachment in emailDTO.FileAttachments)
{
if (emailAttachment.UploadFile != null && emailAttachment.UploadFile.Length > 0)
{
var attachmentItem = new AttachmentItem
{
AttachmentType = AttachmentType.File,
Name = emailAttachment.FileName,
Size = emailAttachment.UploadFile.Length
};
var session = await client
.Users[emailDTO.FromEmail]
.MailFolders
.Drafts
.Messages[draft.Id]
.Attachments
.CreateUploadSession(attachmentItem)
.Request()
.PostAsync();
var stream = new MemoryStream(emailAttachment.UploadFile);
var maxChunkSize = 320 * 1024 * 1024;
var provider = new ChunkedUploadProvider(session, client, stream, maxChunkSize);
var readBuffer = new byte[maxChunkSize];
var chunkRequests = provider.GetUploadChunkRequests();
//var uploadedItem = await provider.UploadAsync();
var trackedExceptions = new List<Exception>();
foreach (var rq in chunkRequests)
{
var result = await provider.GetChunkRequestResponseAsync(rq, readBuffer, trackedExceptions);
}
}
}
}
Error:
{
Code: InvalidAudienceForResource
Message: The audience claim value is invalid for current resource.
Audience claim is 'https://graph.microsoft.com', request url is
'https://outlook.office.com/api/beta/User
I believe the problem here is that the session URL that gets created points to a resource that is not on Microsoft Graph. However, when you use the same client to call that endpoint it passes the bearer token that belongs to Graph. I believe the session URL has an access token in the URL that is sufficient.
You could update your DelegateAuthenticationProvider function to only add the Authorization header for hosts that are graph.microsoft.com. Or you could use our LargeFileUploadTask instead of the ChunkedUploadProvider and it will do much of this work for you. Sadly, I haven't finished the docs for it yet. I'll come back and update this post soon with a docs link.
var task = new Task(() =>
{
foreach(var attachment in attachments) {
using(MemoryStream stream = new MemoryStream()) {
var mimePart = (MimePart)attachment;
mimePart.Content.DecodeTo(stream);
var size = MeasureAttachmentSize(mimePart);
var attachmentItem = MapAttachmentItem(attachment, size);
// Use createUploadSession to retrieve an upload URL which contains the session identifier.
var uploadSession = client.Users[mailbox]
.Messages[addedMessage.Id]
.Attachments
.CreateUploadSession(attachmentItem)
.Request()
.PostAsync()
.GetAwaiter()
.GetResult();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession
,stream
,maxSliceSize
,client);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(prog =>
{
Console.WriteLine($"Uploaded {prog} bytes of {stream.Length} bytes");
});
try {
// Upload the file
var uploadResult = fileUploadTask.UploadAsync(progress, 3).Result;
if(uploadResult.UploadSucceeded) {
// The result includes the location URI.
Console.WriteLine($"Upload complete, LocationUrl: {uploadResult.Location}");
}
else {
Console.WriteLine("Upload failed");
}
}
catch(ServiceException ex) {
Console.WriteLine($"Error uploading: {ex.ToString()}");
throw ex;
}
}
}
});
task.RunSynchronously();

How to add a time stamp on digital signature using cmsSigner

I trying to set the time stamp on my signature using SignedCms, I succeeded in returning the timestamptoken of castle bouncy but I need to implement time stamp of the authorization server on my signature.
I've tried adding UnsignedAttributes but to no avail.
This is my signature code:
static public byte[] SignMsg(Byte[] msg, X509Certificate2 signerCert, bool detached, Arquivo arquivo)
{
ContentInfo contentInfo = new ContentInfo(msg);
SignedCms signedCms = new SignedCms(contentInfo, detached);
CmsSigner cmsSigner = new CmsSigner(signerCert);
cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
NetworkCredential myCred = new NetworkCredential(
"user", "pass");
CredentialCache myCache = new CredentialCache();
myCache.Add(new Uri("http://tsatest2.digistamp.com/tsa"), "Basic", myCred);
UserCredentials user = new UserCredentials(myCred);
var d = RequestTimeStampToken("http://tsatest2.digistamp.com/tsa", arquivo.arquivo,null, user);
var x = d.Time;
var chain = new X509Chain();
System.Security.Cryptography.AsnEncodedData timeData = new Pkcs9AttributeObject(Oid.SHA256.OID, d.EncodedToken);
cmsSigner.UnsignedAttributes.Add(timeData);
signedCms.ComputeSignature(cmsSigner, false);
return signedCms.Encode();
}
This is my response from request:
public static TimeStampToken RequestTimeStampToken(string tsaUri, string pathToFile)
{
return RequestTimeStampToken(tsaUri, pathToFile, null, null);
}
public static TimeStampToken RequestTimeStampToken(string tsaUri, string pathToFileToTimestamp, Oid digestType, UserCredentials credentials)
{
if (null == pathToFileToTimestamp)
{
throw new ArgumentNullException("pathToFileToTimestamp");
}
using (FileStream fs = new FileStream(pathToFileToTimestamp, FileMode.Open, FileAccess.Read))
{
return RequestTimeStampToken(tsaUri, fs, digestType, credentials);
}
}
public static TimeStampToken RequestTimeStampToken(string tsaUri, Stream dataToTimestamp, Oid digestType, UserCredentials credentials)
{
if (null == tsaUri)
{
throw new ArgumentNullException("tsaUri");
}
if (null == dataToTimestamp)
{
throw new ArgumentNullException("dataToTimestamp");
}
if (null == digestType)
{
digestType = Oid.SHA512;
}
byte[] digest = DigestUtils.ComputeDigest(dataToTimestamp, digestType);
Request request = new Request(digest, digestType.OID);
return RequestTST(tsaUri, request, credentials);
}
private static TimeStampToken RequestTST(string tsaUri, Request request, UserCredentials credentials = null)
{
byte[] responseBytes = null;
UriBuilder urib = new UriBuilder(tsaUri);
switch (urib.Uri.Scheme)
{
case "http":
case "https":
responseBytes = GetHttpResponse(tsaUri, request.ToByteArray(), credentials);
break;
case "tcp":
responseBytes = GetTcpResponse(tsaUri, request.ToByteArray());
break;
default:
throw new TimeStampException("Unknown protocol.");
}
Response response = new Response(responseBytes);
ValidateResponse(request, response);
return response.TST;
}
public Response(byte[] response)
{
if (null == response)
{
throw new ArgumentNullException("response");
}
this.response = new TimeStampResponse(response);
if (null != this.response.TimeStampToken)
{
Org.BouncyCastle.Asn1.Tsp.TimeStampResp asn1Response = Org.BouncyCastle.Asn1.Tsp.TimeStampResp.GetInstance(Org.BouncyCastle.Asn1.Asn1Sequence.FromByteArray(response));
var derTst = asn1Response.TimeStampToken.GetDerEncoded();
this.TST = new TimeStampToken(derTst);
}
}
I want to include the time stamp in the digital signature and information that it has been validated by an authorization server.
This works for me.
cmsSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));

HttpClient PostAsycn Always returns 404 Not Found but controller works with Postman and Javascript application

I have a Post Web Api Method in a controller and it's already working, I've been testing the method sending file from postman and a my own web application and it works but now I'm trying to send file from console application using Httpclient but always get 404.
Controller Method
public async Task<IHttpActionResult> Post()
{
FileServerConfig config = FileServerConfiguration.ObtenerConfiguracion(ConfigurationManager.AppSettings);
var path = string.Empty;
try
{
if (!Request.Content.IsMimeMultipartContent())
{
return StatusCode(HttpStatusCode.UnsupportedMediaType);
}
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
foreach (var stream in filesReadToProvider.Contents)
{
IFileServerWrapper _cliente = FileServerConfiguration.CrearCliente(config);
var name = stream.Headers.ContentDisposition.FileName.Replace("\"", "").Replace("\\", "");
var fileUploaded = await _cliente.FileUpload(path, await stream.ReadAsStreamAsync(), name, false);
}
return Ok();
}
catch (HttpException httpex)
{
if (httpex.GetHttpCode() == (int)HttpStatusCode.Conflict)
{
return Conflict();
}
else
{
return InternalServerError(httpex);
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
Console application
static void Main(string[] args)
{
string filePath = #"C:\Software\itextTifftoPDF.rar";
using (var client = new HttpClient())
using (var content = new MultipartFormDataContent())
{
// Make sure to change API address
//client.BaseAddress = new Uri("http://localhost:80/FileServerAPI/");
client.DefaultRequestHeaders.Add("Accept-Language", "en-GB,en-US;q=0.8,en;q=0.6,ru;q=0.4");
client.DefaultRequestHeaders.CacheControl = CacheControlHeaderValue.Parse("no-cache");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Add first file content
var fileContent1 = new ByteArrayContent(File.ReadAllBytes(filePath));
fileContent1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
FileName = "itextTifftoPDF.rar"
};
content.Add(fileContent1);
// Make a call to Web API
var result = client.PostAsync("http://localhost/FileServerAPI/api/File", content).Result;
Console.WriteLine(result.StatusCode);
Console.ReadLine();
}
}
Finally I found a solution. the code is ugly because it was made just to be sure it works but I'll hope it may help others with the same problem.
string filePath = #"C:\temp\webdavtest.txt";
using (HttpClient httpClient = new HttpClient())
{
using (MultipartFormDataContent content =
new MultipartFormDataContent())
{
using (FileStream stream = File.Open(
filePath, FileMode.Open, FileAccess.Read))
{
using (StreamContent streamConent =
new StreamContent(stream))
{
content.Add(
streamConent, "webdavtest.txt", "webdavtest.txt");
var result = await httpClient.PostAsync("http://localhost:3983/api/File?path=/nivel5/", content);
return result.StatusCode.ToString();
}
}
}
}

OAuth in PlayReady License Retrieval in UWP

Any idea how to inject OAuth headers into PlayReadyLicenseAcquisitionServiceRequest so they are included with the BeginServiceRequest()? I can't leverage the license URL's query string, or embed the OAuth token in the body; it has to be in the header of the License retrieval's http request.
I found some great sample code here:
https://www.eyecatch.no/blog/using-playready-and-smooth-streaming-in-a-windows-10-uwp-app/
but this was the magic sauce below (with my header addition):
public static async Task<bool> RequestLicenseManual(PlayReadyLicenseAcquisitionServiceRequest request, params KeyValuePair<string, object>[] headers)
{
Debug.WriteLine("ProtectionManager PlayReady Manual License Request in progress");
try
{
var r = request.GenerateManualEnablingChallenge();
var content = new ByteArrayContent(r.GetMessageBody());
foreach (var header in r.MessageHeaders.Where(x => x.Value != null))
{
if (header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
{
content.Headers.ContentType = MediaTypeHeaderValue.Parse(header.Value.ToString());
}
else
{
content.Headers.Add(header.Key, header.Value.ToString());
}
}
var msg = new HttpRequestMessage(HttpMethod.Post, r.Uri) { Content = content };
foreach (var header in headers)
{
msg.Headers.Add(header.Key, header.Value.ToString());
}
Debug.WriteLine("Requesting license from {0} with custom data {1}", msg.RequestUri, await msg.Content.ReadAsStringAsync());
var client = new HttpClient();
var response = await client.SendAsync(msg);
if (response.IsSuccessStatusCode)
{
request.ProcessManualEnablingResponse(await response.Content.ReadAsByteArrayAsync());
}
else
{
Debug.WriteLine("ProtectionManager PlayReady License Request failed: " + await response.Content.ReadAsStringAsync());
return false;
}
}
catch (Exception ex)
{
Debug.WriteLine("ProtectionManager PlayReady License Request failed: " + ex.Message);
return false;
}
Debug.WriteLine("ProtectionManager PlayReady License Request successfull");
return true;
}

Categories