first commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
using OnlineBankingAuth.Configuration;
|
||||
using OnlineBankingAuth.Parsers;
|
||||
|
||||
namespace OnlineBankingAuth.Services
|
||||
{
|
||||
public class DigitalInsightService : IDigitalInsightService
|
||||
{
|
||||
private readonly SecuritySettings _settings;
|
||||
private readonly IAuthTokenParser _parser;
|
||||
|
||||
public DigitalInsightService(
|
||||
IAuthTokenParser parser,
|
||||
IOptions<SecuritySettings> settings)
|
||||
{
|
||||
_parser = parser;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
public OnlineBankingFormData PopulateOnlineBankingDataFromConfig(OnlineBankingFormData data)
|
||||
{
|
||||
data.Authorization = "Basic " +
|
||||
Convert.ToBase64String(
|
||||
Encoding.UTF8.GetBytes(
|
||||
_settings.DigitalInsightConsumerKey + ":" + _settings.DigitalInsightConsumerSecret
|
||||
)
|
||||
);
|
||||
data.DI_FIID = _settings.DigitalInsightFIID;
|
||||
data.DI_TID = (Guid.NewGuid()).ToString();
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> BuildKeyValuePair(OnlineBankingFormData data)
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{"grant_type", data.GrantType },
|
||||
{"username", data.Username },
|
||||
{"password", data.Password }
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<AuthTokenResponse> LoginUser(OnlineBankingFormData data)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var hc = new HttpClient())
|
||||
{
|
||||
var formData = BuildKeyValuePair(data);
|
||||
using (var content = new FormUrlEncodedContent(formData))
|
||||
{
|
||||
content.Headers.Clear();
|
||||
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
|
||||
HttpContent httpContent = new StringContent(await content.ReadAsStringAsync(), Encoding.UTF8, "application/x-www-form-urlencoded");
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post, _settings.DigitalInsightPostTokenURI);
|
||||
httpRequest.Content = httpContent;
|
||||
|
||||
httpRequest.Headers.Add("Authorization", data.Authorization);
|
||||
httpRequest.Headers.Add("di_fiid", data.DI_FIID);
|
||||
httpRequest.Headers.Add("di_tid", data.DI_TID);
|
||||
|
||||
var response = hc.SendAsync(httpRequest);
|
||||
var xdoc = XDocument.Parse(await response.Result.Content.ReadAsStringAsync());
|
||||
var xel = XElement.Parse(xdoc.ToString());
|
||||
|
||||
var authTokenResponse = _parser.ParseAuthTokenResponse(xel);
|
||||
return authTokenResponse;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Authentication;
|
||||
using System.Xml;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OnlineBankingAuth.Configuration;
|
||||
using OnlineBankingAuth.Services;
|
||||
using OnlineBankingAuth.Parsers;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
|
||||
namespace OnlineBankingAuth.Communication
|
||||
{
|
||||
public class DigitalInsightVendorClient : IDigitalInsightVendorClient
|
||||
{
|
||||
private readonly ISAMLParser<DISAMLAssertion> _samlParser;
|
||||
private readonly SecuritySettings _settings;
|
||||
private readonly IDigitalInsightService _serivce;
|
||||
|
||||
public DigitalInsightVendorClient(
|
||||
ISAMLParser<DISAMLAssertion> samlParser,
|
||||
IOptions<SecuritySettings> settings,
|
||||
IDigitalInsightService service)
|
||||
{
|
||||
_samlParser = samlParser;
|
||||
_settings = settings.Value;
|
||||
_serivce = service;
|
||||
}
|
||||
|
||||
|
||||
public bool ValidateDIPublicKey(XmlElement x509)
|
||||
{
|
||||
var vendorTrustedCert = GetPublicKeyFromStore();
|
||||
|
||||
var innerText = x509.InnerText;
|
||||
byte[] payloadCertBytes = Encoding.Default.GetBytes(innerText);
|
||||
|
||||
var payloadCert = new X509Certificate2(payloadCertBytes);
|
||||
|
||||
return vendorTrustedCert.GetPublicKeyString().Equals(payloadCert.GetPublicKeyString());
|
||||
// @TODO: Assert other equalities besides the "publicKeyString"?
|
||||
}
|
||||
|
||||
public DISAMLAssertion GetSAMLPayload(XmlDocument xmlDocument)
|
||||
{
|
||||
var lmcuPrivateCertificate = GetPrivateKeyFromStore();
|
||||
|
||||
// The "EncryptedAsserion" block is the only acceptable block we can use to construct SAML2.Saml20EncryptedAssertion
|
||||
var encryptedXml = (XmlElement)xmlDocument.GetElementsByTagName("saml:EncryptedAssertion")[0];
|
||||
var encryptedXmlDoc = new XmlDocument();
|
||||
encryptedXmlDoc.LoadXml(encryptedXml.OuterXml);
|
||||
|
||||
// SAML2 library does the decrypting work
|
||||
var privateKeyAlgorithm = (RSA)lmcuPrivateCertificate.GetRSAPrivateKey();
|
||||
var encryptedAssertion = new SAML2.Saml20EncryptedAssertion(privateKeyAlgorithm, encryptedXmlDoc);
|
||||
encryptedAssertion.Decrypt();
|
||||
|
||||
// The decrypted document should be signed by DI. The Vendor Public Key is used to assert that this is the case.
|
||||
var vendorPublicKey = GetPublicKeyFromStore().GetRSAPublicKey();
|
||||
var checkSignature = SAML2.Utils.XmlSignatureUtils.CheckSignature(encryptedAssertion.Assertion, vendorPublicKey);
|
||||
|
||||
if (!checkSignature)
|
||||
{
|
||||
throw new AuthenticationException("Signature does not match therefore the decrypted payload cannot be accepted.");
|
||||
}
|
||||
|
||||
var decryptedAssertion = _samlParser.ParseAssertion(encryptedAssertion.Assertion.DocumentElement);
|
||||
return decryptedAssertion;
|
||||
}
|
||||
|
||||
public bool ValidateTimeStamp(DISAMLAssertion assertion)
|
||||
{
|
||||
return 0 <= DateTime.Compare(assertion.Conditions.NotAfter, DateTime.Now);
|
||||
}
|
||||
|
||||
public string GetSuccessRedirectUrl()
|
||||
{
|
||||
return _settings.SAMLSuccessRedirectRoute;
|
||||
}
|
||||
|
||||
public string GetFailureRedirectUrl(string message)
|
||||
{
|
||||
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(message);
|
||||
var encodedMessage = System.Convert.ToBase64String(plainTextBytes);
|
||||
return _settings.SAMLFailureRedirectRoute + "&msg=" + encodedMessage;
|
||||
}
|
||||
|
||||
private X509Certificate2 GetPrivateKeyFromStore()
|
||||
{
|
||||
// FOR TESTING LOCAL #1345. Note: the second argument is the password for the secure cert file
|
||||
//return new X509Certificate2("Controllers" + Path.DirectorySeparatorChar + "dev.pfx", "Nvisia", X509KeyStorageFlags.Exportable);
|
||||
|
||||
///*
|
||||
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
|
||||
certStore.Open(OpenFlags.ReadOnly);
|
||||
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, _settings.PrivateCertAzureThumbprint, false);
|
||||
|
||||
X509Certificate2 lmcuPrivateCertificate;
|
||||
|
||||
if (certCollection.Count == 0)
|
||||
{
|
||||
throw new AuthenticationException("Could not GET private key from PrivateCertAzureThumbprint");
|
||||
}
|
||||
|
||||
// Should only have 1 cert with that thumbprint
|
||||
lmcuPrivateCertificate = certCollection[0];
|
||||
|
||||
return lmcuPrivateCertificate;
|
||||
//*/
|
||||
}
|
||||
|
||||
private X509Certificate2 GetPublicKeyFromStore()
|
||||
{
|
||||
// FOR TESTING LOCAL #1345
|
||||
//return new X509Certificate2("Controllers" + Path.DirectorySeparatorChar + "pfd-sign.qal1signingcert2018");
|
||||
|
||||
///*
|
||||
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
|
||||
certStore.Open(OpenFlags.ReadOnly);
|
||||
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, _settings.PublicCertAzureThumbprint, false);
|
||||
|
||||
X509Certificate2 vendorTrustedCert;
|
||||
|
||||
if (certCollection.Count == 0)
|
||||
{
|
||||
throw new AuthenticationException("Could not GET public key from PublicCertAzureThumbprint");
|
||||
}
|
||||
|
||||
// Should only have 1 cert with that thumbprint
|
||||
vendorTrustedCert = certCollection[0];
|
||||
|
||||
return vendorTrustedCert;
|
||||
//*/
|
||||
}
|
||||
|
||||
public async Task<AuthTokenResponse> AuthenticateOnlineBankingAccount(OnlineBankingFormData data)
|
||||
{
|
||||
var onlineBankingData = _serivce.PopulateOnlineBankingDataFromConfig(data);
|
||||
return await _serivce.LoginUser(onlineBankingData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
|
||||
namespace OnlineBankingAuth.Services
|
||||
{
|
||||
public interface IDigitalInsightService
|
||||
{
|
||||
Task<AuthTokenResponse> LoginUser(OnlineBankingFormData data);
|
||||
OnlineBankingFormData PopulateOnlineBankingDataFromConfig(OnlineBankingFormData data);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using System.Xml;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
|
||||
namespace OnlineBankingAuth.Services
|
||||
{
|
||||
public interface IDigitalInsightVendorClient
|
||||
{
|
||||
bool ValidateDIPublicKey(XmlElement x509);
|
||||
DISAMLAssertion GetSAMLPayload(XmlDocument xmlDocument);
|
||||
bool ValidateTimeStamp(DISAMLAssertion assertion);
|
||||
string GetSuccessRedirectUrl();
|
||||
string GetFailureRedirectUrl(string message);
|
||||
Task<AuthTokenResponse> AuthenticateOnlineBankingAccount(OnlineBankingFormData data);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user