I'm struggeling a bit with my code to make it possible to use mTLS in the RESTClient. So I'm looking for a way to establish an mTLS connection. Here is my code:
...
def mymTLSservice = "https://my-token-service.example.com"
// Load custom my TrustStore and KeyStore
def pathToKeyStore = "/path/to/keystore.jceks"
def pathToTrustStore = "/path/to/truststore"
def keyStorePW = "myKeyStorePW"
def trustStorePW = "myTrustStorePW"
final char[] pwdKeyStore = keyStorePW.toCharArray()
final char[] pwdTrustStore = trustStorePW.toCharArray()
String keyAlias = "my-mTls-cert-alias" // If you have more than one key
String storeType = "JCEKS"
FileInputStream keyStoreInputStream = new FileInputStream(pathToKeyStore)
FileInputStream trustStoreInputStream = new FileInputStream(pathToTrustStore)
KeyStore ks = KeyStore.getInstance(storeType)
ks.load(keyStoreInputStream, pwdKeyStore)
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType())
trustStore.load(trustStoreInputStream, pwdTrustStore)
Key mTlsPrivateKey = ks.getKey(keyAlias, pwdKeyStore)
Certificate[] mTlsChain = ks.getCertificateChain(keyAlias)
KeyStore mtlsKeyStore = KeyStore.getInstance("jks")
mtlsKeyStore.load(null, null)
mtlsKeyStore.setKeyEntry(keyAlias, mTlsPrivateKey, pwdKeyStore, mTlsChain)
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(mtlsKeyStore, pwdKeyStore)
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
// Create SSLContext
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(
keyManagerFactory.getKeyManagers(),
trustManagerFactory,
new java.security.SecureRandom());
SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sc)
def http = new RESTClient(mymTLSservice)
http.auth.basic 'user', 'password'
// HOW CAN I ADD THE SSLConnectionSocketFactory here
http.handler.failure = { resp -> println "Failure: ${resp.statusLine}" }
// Trying request"
def access_token = ''
try{
http.get(
path : "/v2/token"
)
{ resp, json ->
access_token = json
println "access_token"
}
println resp
} catch(HttpResponseException e) {
r = e.response
println("Success: $r.success")
println("Status: $r.status")
println("Reason: $r.statusLine.reasonPhrase")
println("Could not retrieve an access token from token provider!")
}
How can I correctly add the SSLConnectionSocketFactory to http?
Thank you!
I'm trying to communicate over mTLS with a service.
After long investigation I finally figured it out. Hope it helps someone in the same situation.
In the above snippet we create an SSLContext "sc" which we need to pass to the new defined SSLSocketFactory "sf". Then we create a new "Scheme" definition which will use the newly created SSLSocketFactory "sf" for every "https" request. So the above line were we create a SSLConnectionSocketFactory has to replaced with SSLSocketFactory.
SSLSocketFactory sf = new SSLSocketFactory(sc);
Scheme httpsScheme = new Scheme("https", 443, sf);
And at the end we override the ConnectionManager of the RESTClient with the scheme definition "httpsScheme".
def http = new RESTClient(mymTLSservice)
http.auth.basic 'user', 'password'
http.client.connectionManager.schemeRegistry.register(httpsScheme) // this will add the custom keyStore to every https call
This will cause that during SSL negotiation a client certificate will be presented.
Related
I'm looking for the examples to consume the Sharepoint REST API from a C# Console application (read a Sharepoint list to be more exact). There are some tutorials from MS website but they are incomplete in my opinion. For example, this one doesn't show how to acquire the access token and I cannot find any demo code for that:
https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/complete-basic-operations-using-sharepoint-rest-endpoints
This tutorial is exactly what I need, but the code is not working: https://blog.vgrem.com/2015/04/04/consume-sharepoint-online-rest-service-using-net/
private static CookieContainer GetAuthCookies(Uri webUri, string userName, string password)
{
var securePassword = new SecureString();
foreach (var c in password) { securePassword.AppendChar(c); }
var credentials = new SharePointOnlineCredentials(userName, securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
var cookieContainer = new CookieContainer();
cookieContainer.SetCookies(webUri, authCookie);
return cookieContainer;
}
What doesn't work is this line var authCookie = credentials.GetAuthenticationCookie(webUri);. It returns null all the time even though all the webUri, userName, password are correct.
Can someone point me to the right direction or give me an example of client code? The server is running Sharepoint 2013.
My test code for your reference:
static void Main(string[] args)
{
HttpWebRequest endpointRequest = (HttpWebRequest)HttpWebRequest.Create("http://sp/_api/web");
endpointRequest.Method = "GET";
endpointRequest.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
endpointRequest.Credentials = System.Net.CredentialCache.DefaultCredentials;
//HttpWebResponse endpointResponse = (HttpWebResponse)endpointRequest.GetResponse();
try
{
WebResponse webResponse = endpointRequest.GetResponse();
Stream webStream = webResponse.GetResponseStream();
StreamReader responseReader = new StreamReader(webStream);
string response = responseReader.ReadToEnd();//results
responseReader.Close();
Console.WriteLine(response);
Console.ReadLine();
}
catch (Exception e)
{
Console.Out.WriteLine(e.Message); Console.ReadLine();
}
}
Or you could use this Credentials:
var username = "administrator";
var password = "P#ssw0rd";
var domain = "contoso";
endpointRequest.Credentials=new System.Net.NetworkCredential(username, password, domain);
SharePoint 2013 does not need to generate the access token.
I am using this great method, found in this article https://codingstill.com/2016/01/verify-jwt-token-signed-with-rs256-using-the-public-key/#comment-3232, to validate my Azure idToken, which is signed with RS256.
I noticed that Azure AD changes the public key in a period of time. By reading this article, https://nicksnettravels.builttoroam.com/post-2017-01-24-verifying-azure-active-directory-jwt-tokens-aspx. Therefore, I used HttpClient (c#) to obtain the public key from the URL below, every time when I need to validate the token.
https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}
I did get the result of the public key, which is a string array in x5c key. Below is my code:
public static string GetPublicKeyAsync(string kid)
{
using (var client = new HttpClient())
{
HttpResponseMessage response = client.GetAsync("https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}").Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
// by calling .Result you are synchronously reading the result
string responseBody = responseContent.ReadAsStringAsync().Result;
JObject json = JObject.Parse(responseBody);
string c = json["keys"].ToString();
List<ADPublic> allPublicKeys = JsonConvert.DeserializeObject<List<ADPublic>>(json["keys"].ToString());
foreach (ADPublic key in allPublicKeys)
{
if (key.kid == kid)
{
string certificateString = key.x5c[0];
var certificate = new X509Certificate2(Convert.FromBase64String(certificateString));
string pkey = Convert.ToBase64String(certificate.PublicKey.EncodedKeyValue.RawData);
var x509SecurityKey = new X509SecurityKey(certificate)
{
KeyId = key.kid
};
return pkey;
}
}
}
return null;
}
}
When I pass my public key over to the validation method. I always get an error at the line (PublicKeyFactory.CreateKey(keyBytes) method ):
Please refer to the verify method I mentioned in the beginning of this question for the code below:
// call my get public key function...
string key = GetPublicKeyAsync(headerData["kid"].ToString());
var keyBytes = Convert.FromBase64String(key); // your key here
**AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);**
Unknown object in GetInstance: Org.BouncyCastle.Asn1.DerInteger Parameter name: obj
I think I am almost there, but missing the last part, could you please help? Thank you very much!
I got the same error when using certificate.PublicKey.EncodedKeyValue.RawData to get the public key. I referred to this issue and finally succeeded.
The code shows getting AsymmetricKeyParameter from x5c string. Decode(...) function is referred to in the article.
string token = "<the token that you want to verify>";
string certificateString = "<the first x5c of the first key from https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}>";
byte[] buffer = Convert.FromBase64String(certificateString);
X509CertificateParser parser = new X509CertificateParser();
var _certificate = parser.ReadCertificate(buffer);
AsymmetricKeyParameter publicKey = _certificate.GetPublicKey();
string result = Decode(token, publicKey);
Console.WriteLine("result: " + result);
I have to register a device on IoT hub using DPS service. I cannot use the .net SDK as device firmware does not support so we decided to use the REST based API's to do the same.
With C# SDK all I need are the .PFX file with password, DPS_IDSCOPE and device endpoint something like this (xyz.azure-devices-provisioning.net).
Now How I can use above information to do the same with azure rest API.For Authentication I have seen below link which says I have to use SAS token for the same as Azure AD access token wont work.
https://social.msdn.microsoft.com/Forums/en-US/19183e82-437e-4d6f-8498-ed33ba18a3fa/creating-iot-device-with-azure-dps-via-rest?forum=azureiothub
Now If I trust on above link (However I do not think it will work ) then where is the use of certificate .PFX file ?
I have found this official API to register the device .
https://learn.microsoft.com/en-us/rest/api/iot-dps/runtimeregistration/registerdevice
I have not understand how to pass the body information like structure of JSON.I know I have to use x509 as Attestation type but how I will form is it like
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("registrationId", "device1"),
new KeyValuePair<string, string>("type", "x509"),
};
Or if its a json then what will be the name of attribute ?
Now below are the sample code that I tried to use and getting same error.
Way-1 (Used .PFX as authentication)
public static void RegisterDeviceWithEnrollementGroup()
{
try
{
var handler = new WebRequestHandler();
var certFile = Path.Combine(#"C:\IoT\", "device1.pfx");
handler.ClientCertificates.Add(new X509Certificate2(certFile, "certificatepassword"));
HttpClient client4 = new HttpClient(handler);
client4.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client4.BaseAddress = new Uri("https://XYZ.azure-devices-provisioning.net/scopeid/registrations/device1/register?api-version=2018-11-01");
string content = Newtonsoft.Json.JsonConvert.SerializeObject(null);
var httpContent3 = new StringContent(content, Encoding.UTF8, "application/json");
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("registrationId", "device1"),
new KeyValuePair<string, string>("type", "x509"),
};
var content2 = new FormUrlEncodedContent(pairs);
HttpResponseMessage response4 = client4.PutAsync(client4.BaseAddress.ToString(), content2).Result;
var commandResult = string.Empty;
if (response4.IsSuccessStatusCode)
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
else
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
Console.WriteLine("IoT hub API call result - " + commandResult);
}
catch (Exception)
{
throw;
}
}
Way-2 - Using SAS token :
public static void RegisterDeviceWithEnrollementGroup()
{
try
{
HttpClient client4 = new HttpClient();
var sas = generateSasToken("XYZ.azure-devices-provisioning.net", "key", "provisioningserviceowner");
client4.DefaultRequestHeaders.Add("Authorization", sas);
client4.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client4.BaseAddress = new Uri("https://XYZ.azure-devices-provisioning.net/scopeid/registrations/device1/register?api-version=2018-11-01");
string content = Newtonsoft.Json.JsonConvert.SerializeObject(null);
var httpContent3 = new StringContent(content, Encoding.UTF8, "application/json");
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("registrationId", "device1"),
new KeyValuePair<string, string>("type", "x509"),
};
var content2 = new FormUrlEncodedContent(pairs);
HttpResponseMessage response4 = client4.PutAsync(client4.BaseAddress.ToString(), content2).Result;
var commandResult = string.Empty;
if (response4.IsSuccessStatusCode)
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
else
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
Console.WriteLine("IoT hub API call result - " + commandResult);
}
catch (Exception)
{
throw;
}
}
Helper method :
public static string generateSasToken(string resourceUri, string key, string policyName, int expiryInSeconds = 3600)
{
TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds);
string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry);
if (!String.IsNullOrEmpty(policyName))
{
token += "&skn=" + policyName;
}
return token;
}
Now Please answer some body whether I am doing correct or wrong here as I am getting exception.
{StatusCode: 415, ReasonPhrase: 'Unsupported Media Type', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
x-ms-request-id: 6475343d-5a2e-407a-9e7f-896e0c489307
Strict-Transport-Security: max-age=31536000; includeSubDomains
Date: Thu, 28 Feb 2019 11:42:59 GMT
Content-Length: 0
}}
Looking forward for the help ...
Please follow the steps outlined here:
https://learn.microsoft.com/en-us/azure/iot-dps/tutorial-net-provision-device-to-hub
to first create an Enrollment in DPS for this device using X.509 (no need to use EnrollmentGroup for a single device).
When registering a device with DPS, use the global endpoint - global.azure-devices-provisioning.net. Configure the HTTP client to include the device client certificate. Do not provide a SAStoken from the device.
You can setup the HTTP message content for device registration as follows:
httpRequest.Content = new StringContent("{\"registrationId\": \"device1\"}", Encoding.UTF8);
httpRequest.Content.Headers.ContentType=MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
Note that the JSON content does NOT include "type" : "x509".
The device endpoint would be as follows:
PUT https://global.azure-devices-provisioning.net/{idScope}/registrations/device1/register?api-version=2018-11-01 (replace idScope with the one from your DPS. A sample idScope would be 0ne00000012)
This POST explains it quite well: http://busbyland.com/azure-iot-device-provisioning-service-via-rest-part-1/
Using the endpoint documented here: https://learn.microsoft.com/es-es/rest/api/iot-dps/runtimeregistration/registerdevice
You can manage to register your devices with a HTTPRequest see the CURL Example above:
curl -L -i -X PUT --cert ./chain.pem --key ./iot-device-1.key.pem -H 'Content-Type: application/json' -H 'Content-Encoding: utf-8' -d '{"registrationId": "iot-device-1", "payload": {"CustomProperty": "CustomValue"}}' https://global.azure-devices-provisioning.net/XXXXXXXXXXX/registrations/iot-device-1/register?api-version=2019-03-31
You could replicate that request in your development environment.
Note that the global.azure-devices-provisioning.net is used which is public and you do not need any authentication/authorization.
I have disabled sslv3 in server side like this :
char certPass[] = "***";
char certAliaMainPass[] = "***";;
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(certPath), certPass);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, certAliaMainPass);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(iPort);
String[] protocols = sslServerSocket.getEnabledProtocols();
Set<String> set = new HashSet<String>();
for (String s : protocols) {
if (s.equals("SSLv3")) {
continue;
}
set.add(s);
}
sslServerSocket.setEnabledProtocols(set.toArray(new String[0]));
but client which used "SSLv3" still can connect to server, how can I do for this issue?
Go to Java installation folder.
Open {JRE_HOME}\lib\security\java.security -file in text editor.
Go to the last line.
Delete or comment out the following line jdk.tls.disabledAlgorithms=SSLv3
I'm creating Organization service proxy object using following way:
[ThreadStatic]
public static OrganizationServiceProxy OrgServiceProxy;
// ...
sLog.DebugFormat("Get AuthenticationProviderType...");
AuthenticationProviderType _crmAuthType = this.GetServerType(parameters.DiscoveryUri);
sLog.DebugFormat("Get AuthenticationProviderType - DONE!");
// ...
sLog.Info("Perform metadata download (ServiceConfigurationFactory.CreateConfiguration)...");
IServiceConfiguration<IOrganizationService> _crmServiceConfiguration = ServiceConfigurationFactory.CreateConfiguration<IOrganizationService>(parameters.OrgServiceUri);
sLog.Info("Perform metadata download (ServiceConfigurationFactory.CreateConfiguration) - DONE");
// ...
// enable proxy types
var behavior = new ProxyTypesBehavior() as IEndpointBehavior;
behavior.ApplyClientBehavior(_crmServiceConfiguration.CurrentServiceEndpoint, null);
// ...
public OrganizationServiceProxy GetServiceProxy(ICRMConnectionParameters parameters)
{
// ...
ClientCredentials clientCreds = new ClientCredentials();
clientCreds.Windows.ClientCredential.UserName = parameters.UserName;
clientCreds.Windows.ClientCredential.Password = parameters.Password;
clientCreds.Windows.ClientCredential.Domain = parameters.Domain;
sLog.DebugFormat("Setup client proxy...");
OrgServiceProxy = new OrganizationServiceProxy(_crmServiceConfiguration, clientCreds);
sLog.DebugFormat("Setup client proxy - DONE.");
return OrgServiceProxy;
}
Just note here that AuthenticationProviderType and IServiceConfiguration are statically cached. This code above is part of class named CRMConnection.
I have one more abstract class (ProxyUser) which contains following property:
private CRMConnection conn;
// ...
protected OrganizationServiceProxy OrgServiceProxy
{
get
{
//return orgService;
return this.Conn.GetServiceProxy();
}
}
protected CRMConnection Conn
{
get
{
conn = conn ?? new CRMConnection();
return conn;
}
}
In another class that inherits ProxyUser I have method with following code:
ColumnSet columnSet = new ColumnSet();
ConditionExpression condition1 = new ConditionExpression("new_id", ConditionOperator.NotNull);
FilterExpression filter = new FilterExpression(LogicalOperator.And);
filter.AddCondition(condition1);
QueryExpression query = new QueryExpression()
{
EntityName = new_brand.EntityLogicalName,
ColumnSet = columnSet,
Criteria = filter,
NoLock = true
};
EntityCollection res = OrgServiceProxy.RetrieveMultiple(query);
And now we come to the point :)
If I setup correct parameters - organization service url, discovery service url, username, password and domain, everything works as expected. BUT, in case when wrong password is set, in line below, service is simply unresponsive. It doesn't happen anything.
EntityCollection res = OrgServiceProxy.RetrieveMultiple(query);
Of course, I'm expecting authentication failed error. Any suggestions what I'm missing here?
Thanks in advance!
I solved this problem with adding line below in GetServiceProxy method - when ClientCredentials are created:
clientCreds.SupportInteractive = false;
I figured this out after I moved whole logic in console app. When wrong password is set and app is in debug mode, I'm getting windows login prompt. Then I found this answer.