How can i get server certificate information when using IWinHttpRequest (or its IServerXMLHTTPRequest wrapper)?
WinHttp example
IWinHttpRequest http = new WinHttpRequest();
http.Open("GET", "https://example.com", false);
http.send(null);
MSXML example
//IServerXmlHttpRequest internally is a wrapper around WinHttp
IServerXmlHttpRequest http = new ServerXmlHttp60();
http.open("GET", "https://example.com/", false);
http.send(null);
Now that i've sent the request, how can i get information about the server certificate?
Specifically:
i need it's thumbprint (e.g., 43c930419a3adf8ae1e9a635e078ae62e2c7ab4b)
i would like it's subject (e.g., CN = *.silkroad.onion)
i would like it's issuerer (e.g., CN = DigiCert SHA2 Secure Server CA)
It goes without saying that this is not using the flat C WinHttp API, but is using the COM object.
Bonus Reading
InternetQueryOption function
INTERNET_OPTION_SECURITY_CERTIFICATE / INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT
INTERNET_CERTIFICATE_INFO structure
Related
I use HttpClient to talk to my WebAPI service. For SSL authentication, I set up the client certificates on the HttpClient using WebRequestHandler -
private static WebRequestHandler CreateWebRequestHandler(List<X509Certificate2> clientCertificates)
{
WebRequestHandler handler = new WebRequestHandler();
if (clientCertificates != null && clientCertificates.Any())
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
clientCertificates.ForEach(cert => handler.ClientCertificates.Add(cert));
}
return handler;
}
On the Service, I have a custom DelegatingHandler to validate the client certificates using thumbprint -
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
X509Certificate2 certificate = request.GetClientCertificate();
// Code to validate certificate's Thumbprint with white listed thumbprints
}
From the HttpRequest, I can get only one client certificate.
My question: Why does WebRequestHandler allow a collection of ClientCertificates to be set? Does it present all the client certificates to the server? If yes, then how do I get the list of client certificates in the DelegatingHandler?
Actually, only one certificate is send to server by a client during TLS\SSL handshake which you are obtain on the Server. The process of choosing this certificate is well described here.
Briefly explanation is - the client will choose the best suitable certificate from X509CertificateCollection looking for a match between the list of certificate issuers provided by the server and the client certificate issuer name. The first certificate that matches is sent to the server. If no certificate matches or the certificate collection is empty, then an anonymous credential is sent to the server. The deeper mechanism of TLS\SSL work described in a good manner here
I am trying to build a Ruby Daemon service to access the Office 365 rest API. It was recently made possible to do this via the OAuth 'client_credentials' flow, as detailed in this blog post: https://learn.microsoft.com/en-us/archive/blogs/exchangedev/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow
I am struggling to generate a valid access token. The token endpoint returns me a JWT however when using this token I received a 401 with this message:
The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2
I understand that the client_credentials flow requires you to present a X.509 cert, unfortunately all the examples in the blog post are for C#.
I am using a generated self signed cert and private key to do a client assertion when requesting the token. I followed the steps in the blog post to generate the cert and update the manifest to use this cert.
This is the ruby code for reference:
def request_token
uri = URI.parse("https://login.windows.net/== TENANT-ID ==/oauth2/token?api-version=1.0")
https = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.request_uri)
req.set_form_data(
:grant_type => 'client_credentials',
:redirect_uri => 'http://spready.dev',
:resource => 'https://outlook.office365.com/',
:client_id => '== Client ID ==',
:client_secret => '== Client secret =='
)
https.use_ssl = true
https.cert = client_cert
https.key = client_key
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
resp = https.start { |cx| cx.request(req) }
#access_token = JSON.parse(resp.body)
end
Obviously I have removed certain bits of information for security. Even though it is ruby you can see I am using my cert to validate the client using an SSL connection.
Here's some more infomation on the error:
"x-ms-diagnostics" => "2000010;
reason=\"The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2.\";
error_category=\"insufficient_auth_strength\"",
"x-diaginfo"=>"AM3PR01MB0662",
"x-beserver"=>"AM3PR01MB0662"
Any help would be appreciate.
Edit
For others looking to do something similar in Ruby here's a Gist of the code I use: https://gist.github.com/NGMarmaduke/a088943edbe4e703129d
The example uses a Rails environment but it should be fairly easy to strip out the Rails specific bits.
Remember to replace YOUR CLIENT ID, TENANT_ID and CERT_THUMBPRINT with the correct values and point the cert path and client key methods to the right file path.
Then you can do something like this:
mailbox = OfficeAPI.new("nick#test.com")
messages = mailbox.request_messages
Instead of a client_secret in your request body, you need a client_assertion. This is a bit more complex, but it's the reason you need that certificate.
Basically you need to build a JSON Web Token and sign it with your certificate using a SHA256 hash. The token is going to look something like this:
Header:
{
"alg": "RS256",
"x5t": "..." // THUMBPRINT of Cert
}
Payload:
{
"aud": "https:\\/\\/login.windows.net\\/<The logged in user's tenant ID>\\/oauth2\\/token",
"exp": 1423168488,
"iss": "YOUR CLIENT ID",
"jti": "SOME GUID YOU ASSIGN",
"nbf": 1423167888,
"sub": "YOUR CLIENT ID"
}
If you're still with me, you now need to base64-encode both pieces (separately), then concatenate them with a '.'. So now you should have:
base64_header.base64_payload
Now you take that string and sign it with your certificate, using a SHA256 hash. Then base64-encode the result of that, url-encode it, then append to the string, so now you have:
base64_header.base64_payload.base64_signature
Finally, include this in your POST to the token endpoint as the client_assertion parameter, and also include a client_assertion_type parameter set to "urn:ietf:params:oauth:client-assertion-type:jwt-bearer":
req.set_form_data(
:grant_type => 'client_credentials',
:redirect_uri => 'http://spready.dev',
:resource => 'https://outlook.office365.com/',
:client_id => '== Client ID ==',
:client_assertion_type => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
:client_assertion => 'base64_header.base64_payload.base64_signature'
)
I hope that helps! This is all based on my research into how ADAL does it, and I haven't tested it myself in Ruby.
I just managed to get this working, so I thought I'd throw one more piece of advice into the mix. All the instruction articles out there say that you should add your certificate to the manifest file. I had trouble with that, but here is what I did that finally made it work:
In Azure, go to Settings > Management Certificates
Upload the public key as a .cer file (google around if you don't know how to convert it). This should be a binary file that your text editor barfs on.
Now that it's uploaded, Microsoft will give you the thumbprint. It's in the "Thumbprint" column. But, it's in hex, not base64. So, convert it like this:
# Hint: use your actual thumbprint, not this fake one
echo '5292850026FADB09700E7D6C1BCB1CD1F3270BCC' | xxd -r -p | base64
Finally, use this base64 encoded thumbprint as the value for x5t in the JSON header.
I added a function in HomeController on the git to demo how to request an access token by hand using client assertion w/o ADAL. It might be easier to port using this: https://github.com/mattleib/o365api-as-apponly-webapp/commit/12d5b6dc66055625683020576139f5771e6059e1
Just some additions: The audience claim in the assertion is the same as the endpoint you address with the token request. As Jason correctly identified, this is the token endpoint of AAD: https://login.windows.net/{the tenant you want an app token for}/oauth2/token. Also the nbf and exp are the time you created the assertion in unix epoche time, e.g. in .net you would do something like "WebConvert.EpocTime(DateTime.UtcNow)". For "not before" (nbf) maybe subtract a buffer for clock skew, e.g. 5 minutes; and for expires in (exp) add some time, e.g. 15 minutes (so the assertion remains valid for that time).
Here is a fiddler trace of a token request (raw):
POST https://login.windows.net/0e49ef1f-ca07-45f1-b4c0-ac9409d3e576/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client-request-id: a8108f88-275b-424d-ac28-f675aabe548e
return-client-request-id: true
x-client-SKU: .NET
x-client-Ver: 2.12.0.0
x-client-CPU: x64
x-client-OS: Microsoft Windows NT 6.2.9200.0
Host: login.windows.net
Content-Length: 983
Expect: 100-continue
Connection: Keep-Alive
resource=https%3A%2F%2Fgraph.windows.net%2F&client_id=f17bb8a5-2bef-4ad5-a83f-cd7113449fc2&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6ImY4S2JVY0xtMnItS2s4b1Z3ZVZYTFU0NzhJcyJ9.eyJhdWQiOiJodHRwczpcL1wvbG9naW4ud2luZG93cy5uZXRcLzBlNDllZjFmLWNhMDctNDVmMS1iNGMwLWFjOTQwOWQzZTU3Nlwvb2F1dGgyXC90b2tlbiIsImV4cCI6MTQyMjk4NDMzNSwiaXNzIjoiZjE3YmI4YTUtMmJlZi00YWQ1LWE4M2YtY2Q3MTEzNDQ5ZmMyIiwianRpIjoiZTI3OTA5YTctZGYwMC00NjBhLTlmZjctOGZkNDExOWVmNTYzIiwibmJmIjoxNDIyOTgzNzM1LCJzdWIiOiJmMTdiYjhhNS0yYmVmLTRhZDUtYTgzZi1jZDcxMTM0NDlmYzIifQ.g9bo4-lxpNJ4kEOMuQxODU-5iakwSVIzyRQEPLdbpuNn_XD4lcvt2yBIWT12EQaUVKkMyqFrDiIh4Oav565-Po7HfhmSPF3URXVj8Kx5lx17Zh0nWiaNkRXEi1vhwswsfjm1o-8B8LGUJTtT6JXTognrueuSL1aEE_-4qSG1y74aoc949Un1pQCjwuBtao4vs4CPJLu9Y9mVbirVRRtiIfxkUMmzf6yfMtuhugoGmrvUYntUo4x6N2fu4LxGjuIs7czyrMMAmDRo-XK4sAhDo5uof10HKb8ETEU8mhObwNZcz86MYHWbZm3Z_HDOwzC9kA_tp6hWqmlJ3c-gLg5VXA&grant_type=client_credentials
Hope this helps!
Good luck!
Matthias
I am changing some Ruby code to use the https scheme, but I wasnt happy about setting the SSL verification policy to VERFIY_NONE.
How do I validate the SSL cert from a remote server is genuine ?
Say you want to retrieve a resource from an HTTPS server.
Many people chose to set the HTTP verify_mode to OPENSSL::SSL::VERIFY_NONE to bypass any
SSL sertificate validation, but getting Ruby to validate an SSL certificate is pretty easy.
Note some people use OpenSSL::X509::Store#set_default_paths to use the default trusted certificates for a system.
If this works for you, cool, but in my case the trusted signing certificate wasnt in the system store. I needed a
way to validate an internal sites SSL cert using the internally generated, public signing certicate.
One thing you absolutely will need is the public certificate for the entity that signed the sites SSL certificate. In my
case, I had the cert file, but I needed to convert it from DER X.509 certificate (.cer file) to a Base-64 encoded
X.509 certificate, then renaming the CER extension to PEM. Most platforms (Windows, Linux, OSX etc) have certificate management tools for importing and exporting certificates. This was required because Ruby seems to have issues with natively consuming DER certificates.
Once I had the PEM file I wrote the following code
url = URI.parse(https_url)
http = Net::HTTP.new(url.host, url.port)
if url.scheme == 'https'
http.use_ssl = true
# most people use VERIFY_NONE - change to force cert check
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
#load the cert file
cert = Pathname.new(Dir.pwd) + "cert.pem"
fail "no certificate file found" unless cert.exist?
store = OpenSSL::X509::Store.new
store.add_cert(OpenSSL::X509::Certificate.new(File.read(cert)))
http.cert_store = store
end
# get the resource, validating the remote SSL cert first
response = http.request(Net::HTTP::Get.new(url.request_uri))
I have written the following code and when I run the scheduler, get this error : "A certificate is required to complete client authentication, msxml3.dll error '80072f0c'".
Dim objXML
Set objXML = CreateObject ("MSXML2.ServerXMLHttp")
objXML.open "GET", "https://test.moneymapwebpro.com/synchClients.aspx", false
objXML.send
Set objXML = Nothing
Please let me know , why the error message occurs.
According to Microsoft KB302080:
Secure Sockets Layer (SSL) certificate support was added to the
ServerXMLHTTP request object with the release of MSXML version 3.0
Service Pack 1. To make any SSL requests from the Web server,
ServerXMLHTTP expects a client digital certificate to be installed,
even if the target Web server does not require a client certificate.
You need to use to a new version of MSXML -- at least MSXML 3.0 SP2 -- and install a client certificate.
i am going to present my problem:
My web application submit a form by https to a server that it needs client certificate for set the communication. It works fine, when i submit the form, the https server asks me for a certificate, i select my certificate and the server response me. But now, i want to post to https server from a servlet with HttpClient because i want to manage the response from https server, and here i have problems...
My code to send a post with HttpClient is:
DefaultHttpClient client = new DefaultHttpClient();
// in the truststore i have the https server certificates for establish connection
// from my client to https server
FileInputStream instream = new FileInputStream(new File(TRUESTORE_PATH));
KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
truststore.load(instream, KEY_TRUESTORE_PASS.toCharArray());
// in the keystore i have the client certificate but without private key because,
// in theory, i shouldn't know it
FileInputStream otherinstream = new FileInputStream(new File(KEYSTORE_PATH));
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(otherinstream,KEY_STORE_PASS.toCharArray());
SSLSocketFactory socketFactory = new SocketFactory(keystore,KEY_STORE_PASS,truststore);
Scheme sch = new Scheme("https", 443, socketFactory);
client.getConnectionManager().getSchemeRegistry().register(sch);
HttpGet httpget = new HttpGet("https://remote_server_with_client_authentication");
HttpResponse response = client.execute(httpget);
When i execute this code, in an unit test for example, i have response from https server, but it tells me that i am not authorized. I suspect that i can't authenticate with the https server because the keystore has the client certificate but whitout private key, then i am not authorized.
Then, Is there some way to establish communication with the https server with a client certificate selected by the user?
Thanks!