Connection to AWS IoT with M2MQTT from .net core - mqtt

I managed to manually create the AWS IoT config, downloaded the certs and create a console app that could subscribe to a topic. Im now trying to automate the thing creation, which results in the certificate keys being provided by AWS as strings. Im not sure how to use these. I have the root ca downloaded already, which I assume I use for all things.
My file based cert subscriber looks like this:
Console.WriteLine("AWS IOT Dotnet core message listener starting");
string iotendpoint = "blahblah-ats.iot.ap-southeast-2.amazonaws.com";
int BrokerPort = 8883;
string Topic = "topic_1/";
var CaCert = X509Certificate.CreateFromCertFile(#"root-CA.crt");
var ClientCert = new X509Certificate2(#"device.pfx", "password");
var IotClient = new MqttClient(iotendpoint, BrokerPort, true, CaCert, ClientCert, MqttSslProtocols.TLSv1_2);
try
{
IotClient.Connect(Guid.NewGuid().ToString());
Console.WriteLine("Connected to AWS IOT");
IotClient.MqttMsgPublishReceived += Client_MqttMsgPublishReceived;
IotClient.MqttMsgSubscribed += Client_MqttMsgSubscribed;
IotClient.Subscribe(new string[] { Topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE });
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
To load the cert from file, I tried this:
var keyText = File.ReadAllText("keys.json");
var keys = JsonConvert.DeserializeObject<Keys>(keyText);
var bytes = Encoding.ASCII.GetBytes(keys.PrivateKey.ToCharArray());
var ClientCert = new X509Certificate2(bytes);
with:
class Keys {
public string PublicKey {get;set;}
public string PrivateKey {get;set;}
}
and the keys from AWS in a json file :
{
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4mh2PQ581XN9BmoCvDjlaktm/6gQgqGBItZThcQVMTjveU8H\npjOU2E/9lq7vmdO+96NuuMr9MKtFD+ZWtVExLjMq9hH0MvIvosVt9+6Ggcwz7Kdr\nigprfBMVORV0rgcK+nsd2DmBNrs339fqbTn5UAIFFBpqkNReW7LMl9h6g8hu4aYQ\nJTohDwSmgmNJKlzMJGtVfPggqt+bBi3lUf9NEOEz...
-----END RSA PRIVATE KEY-----\n",
"PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4mh2PQ581XN9BmoCvDjl\naktm/6gQgqGBItZThcQVMTjveU8HpjOU2E/9lq7vmdO+96NuuMr9MKtFD+ZWtVEx\nLjMq9hH0MvIvosVt9+6Ggcwz7K...
-----END PUBLIC KEY-----\n"
}
Im getting an error loading the cert:
An unhandled exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException' occurred in System.Security.Cryptography.X509Certificates.dll: 'Cannot find the requested object.'
Can anyone see anything obviously wrong here? I dont understand certificates...
Update:
Using the PEM text produced by the AWS SDK is more correct, but I still get an error connecting - M2MQTT says there is a cert problem, it has no private key. Does it need it?
var pemText = File.ReadAllText("thing.crt");
var bytes = Encoding.ASCII.GetBytes(pemText);
var ClientCert = new X509Certificate2(bytes);

Final hacked together solution looks like this:
var keyText = File.ReadAllText("keys.json"); // saved from AWS SDK when creating IoT Cert.
var keys = JsonConvert.DeserializeObject<Keys>(keyText);
var rsa = RsaHelper.PrivateKeyFromPem(keys.PrivateKey);
var pemText = File.ReadAllText("thing.crt");
var bytes = Encoding.ASCII.GetBytes(pemText);
var ClientCert = new X509Certificate2(bytes);
ClientCert = ClientCert.CopyWithPrivateKey(rsa);
ClientCert = new X509Certificate2(ClientCert.Export(X509ContentType.Pfx,"12345678"), "12345678");
RSAHelper from https://github.com/dejanstojanovic/dotnetcore-token-authentication/blob/asymmetric_rsahelper/Sample.Core.Common/Helpers/RsaHelper.cs
Last trick to Export and Import the PFX from https://github.com/aspnet/KestrelHttpServer/issues/2960 to solve error: "No credentials are available in the security package"
Sidebar - why do we (as an industry) always take something conceptually simple and make it so fricken complicated? :)

Related

LibGit2Sharp-SSH "Failed to start SSH session: Unable to exchange encryption keys"

I'm using the current version of LibGit2Sharp-SSH from https://github.com/leobuskin/libgit2sharp-ssh
I generated a SSH private and public key using OpenSSH.
I set up an OpenSSH Server, and am able to use Git Bash to Clone/Push etc. using SSH.
I'm having difficulties Cloning a repository using the SSH protocol through LibGit2Sharp.
I've went through all the similar questions and tried all the answers without any luck.
public CloneOptions cloningSSHAuthentication(string username, string path_to_public_key_file, string path_to_private_key_file)
{
CloneOptions options = new CloneOptions();
SshUserKeyCredentials credentials = new SshUserKeyCredentials();
credentials.Username = username;
credentials.PublicKey = path_to_public_key_file;
credentials.PrivateKey = path_to_private_key_file;
credentials.Passphrase = "passphrase";
options.CredentialsProvider = new LibGit2Sharp.Handlers.CredentialsHandler((url, usernameFromUrl, types) => credentials);
return options;
}
public void CloneRepo(string remotePath, string localPath)
{
var sshDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh");
var PublicKey = Path.Combine(sshDir, "id_rsa.pub");
var PrivateKey = Path.Combine(sshDir, "id_rsa");
CloneOptions options = cloningSSHAuthentication("UserName", PublicKey, PrivateKey);
Repository.Clone(remotePath, localPath, options);
}
I get a "Failed to start SSH session: Unable to exchange encryption keys" exception.
Has anyone had a similar experience or know what I could possibly be missing?

"Export" x509 certificate in Ruby

I'm communicating with an API that has the following directions:
Install the issued x509 certificate onto the client server.
Export the x509 certificate using the supplied password and default Machine Key Set into memory.
Base64 encode the exported bytes of the x509 certificate.
Add ‘X509Certificate’ as an HTTP header and set its value to the result of step 3.
Step 1 and 4 are easy, but I have no idea on 2 or or the 'export' portion of 3. I have tried Googling for some time and I'm not sure exactly where to even really start.
Would someone point me in the right direction on how to "export" a certificate with "machine key set"?
Here is what I have so far
raw_data = File.read('cert.pfx')
pkcs = OpenSSL::PKCS12.new(raw_data, 'password')
cert = OpenSSL::X509::Certificate.new(pkcs.certificate.to_pem)
Here is equivalent .NET code:
public string GetBase64Cert(string certificateThumbprint)
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var foundCertificates = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, false);
if (foundCertificates.Count != 1)
{
return null;
}
var certByteArray = foundCertificates[0].Export(X509ContentType.Cert);
store.Close();
return Convert.ToBase64String(certByteArray);
}
}
And equivalent PHP code:
public function setx509($x509file) {
$cert = openssl_x509_parse($x509file);
$base64cert = base64_encode($cert);
return $base64cert;
}
Try
pkcs = OpenSSL::PKCS12.new(File.read('cert.pfx'), 'password')
str = Base64.urlsafe_encode64(pkcs.certificate.to_der)
Probably also str.gsub(/=+$/, '') to cut off padding

Cannot store Certificate to KeyChain using SecKeyChain in Xamarin

After hours spent reading through what's available online to fix this, I decided to post my question here.
My goal is simple: Store an X509Certficate to KeyChain using Xamarin for iOS. This is a self signed certificate that I generated using BouncyCastle library.
I'm successfuly importing it, but when saving to KeyChain using SecKeyChain.Add, the result is always SecStatusCode.Paramwhich the documentation explains is missing or invalid parameter. Here's the method I use
public static bool StoreCertInKeyChain(X509Certificate2 certificate, string password)
{
var data = certificate.Export(X509ContentType.Pkcs12, password);
var options = NSMutableDictionary.FromObjectAndKey(FromObject(password), SecImportExport.Passphrase);
var statusCode = SecImportExport.ImportPkcs12(data, options, out NSDictionary[] result);
if (statusCode != SecStatusCode.Success) return false;
var certChain = result[0][SecImportExport.CertChain];
var record = new SecRecord(SecKind.Certificate)
{
Label = "MyKey",
Account = "Certificate",
ApplicationTag = "MyTag"
};
record.SetValueRef(certChain);
// Using the below code instead, produces the same result
// var cert = new SecCertificate(certChain.Handle);
// record.SetValueRef(cert);
var resultAdd = SecKeyChain.Add(record);
return resultAdd == SecStatusCode.Success;
}
Has anyone ran into this problem? I'm out of ideas what else to try. I followed the examples given on Xamarin documentation site, without success. Thank you
Answering my solution here, in case anyone else runs into the same issue. The problem was that the certificate supplied in the SecRecord wasn't an instance of SecCertificate, so using SecImportExport.ImportPkcs12 was the wrong way to do it. I ended up using SecIdentity.Import instead, which gives a reference to the certificate as well as the private key in it. The certificate and the private key need to be added to key chain separately using an identity. Here's the code that accomplishes this.
var identity = SecIdentity.Import(certificate.Export(X509ContentType.Pkcs12, password), password);
var storedCertificate = SecKeyChain.QueryAsConcreteType(new SecRecord(SecKind.Certificate) { Label = "My Cert" }, out SecStatusCode statusCode);
if (statusCode != SecStatusCode.Success)
{
var record = new SecRecord(SecKind.Certificate);
record.Label = "My Cert";
record.SetValueRef(identity.Certificate);
var result = SecKeyChain.Add(record);
SecKeyChain.AddIdentity(identity);
storedCertificate = SecKeyChain.QueryAsConcreteType(new SecRecord(SecKind.Certificate) { Label = "My Cert" }, out statusCode);
}
var storedIdentity = SecKeyChain.FindIdentity(storedCertificate as SecCertificate);
The certificate can be retrieved using the label, but to get the private key, the identity must be queried using the certificate as parameter in SecKeyChain.FindIdentity. From this point on, access to signing and decryption on the private key is available from the identity instance.

java.io.FileNotFoundException for Cloud Iot core Code

I am presently working on program on Android Things for connecting to Google Cloud IoT Core. I used to sample maven code provided by Google and modified it for Gradle(with all the imports and stuff). After doing every kind of check, whenever I am trying to run the program on a Raspberry Pi3 running Android Things it keeps giving this error
W/System.err: java.io.FileNotFoundException: com/example/adityaprakash/test/rsa_private.pem (No such file or directory)
telling me that the private key file that I am supposed to use for the JWT doesn't exist despite the fact it does and I have given the path for the pem file.Here are my java codes
package com.example.adityaprakash.test;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
Log.i("#########","######");
MqttExample mqtt = new MqttExample();
try {
mqtt.Start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
The MqttExample.java
package com.example.adityaprakash.test;
// [END cloudiotcore_mqtt_imports]
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.joda.time.DateTime;
import java.io.BufferedReader;
import java.io.FileReader;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import android.util.Base64;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class MqttExample {
// [START cloudiotcore_mqtt_createjwt]
/** Create a Cloud IoT Core JWT for the given project id, signed with the given RSA key. */
public static String createJwtRsa(String projectId, String privateKeyFile) throws Exception {
DateTime now = new DateTime();
String strKeyPEM = "";
BufferedReader br = new BufferedReader(new FileReader(privateKeyFile));
String line;
while ((line = br.readLine()) != null) {
strKeyPEM += line + "\n";
}
br.close();
// Create a JWT to authenticate this device. The device will be disconnected after the token
// expires, and will have to reconnect with a new token. The audience field should always be set
// to the GCP project id.
JwtBuilder jwtBuilder =
Jwts.builder()
.setIssuedAt(now.toDate())
.setExpiration(now.plusMinutes(20).toDate())
.setAudience(projectId);
String privateKeyPEM = strKeyPEM;
privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
byte[] encoded = Base64.decode(privateKeyPEM,Base64.DEFAULT);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
}
/** Parse arguments, configure MQTT, and publish messages. */
public void Start() throws Exception {
// [START cloudiotcore_mqtt_configuremqtt]
MqttExampleOptions options = MqttExampleOptions.values();
if (options == null) {
// Could not parse.
System.exit(1);
}
// Build the connection string for Google's Cloud IoT Core MQTT server. Only SSL
// connections are accepted. For server authentication, the JVM's root certificates
// are used.
final String mqttServerAddress =
String.format("ssl://%s:%s", options.mqttBridgeHostname, options.mqttBridgePort);
// Create our MQTT client. The mqttClientId is a unique string that identifies this device. For
// Google Cloud IoT Core, it must be in the format below.
final String mqttClientId =
String.format(
"projects/%s/locations/%s/registries/%s/devices/%s",
options.projectId, options.cloudRegion, options.registryId, options.deviceId);
MqttConnectOptions connectOptions = new MqttConnectOptions();
// Note that the the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we
// explictly set this. If you don't set MQTT version, the server will immediately close its
// connection to your device.
connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
// With Google Cloud IoT Core, the username field is ignored, however it must be set for the
// Paho client library to send the password field. The password field is used to transmit a JWT
// to authorize the device.
connectOptions.setUserName("unused");
System.out.println(options.algorithm);
if (options.algorithm.equals("RS256")) {
connectOptions.setPassword(
createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
}else {
throw new IllegalArgumentException(
"Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
}
// [END cloudiotcore_mqtt_configuremqtt]
// [START cloudiotcore_mqtt_publish]
// Create a client, and connect to the Google MQTT bridge.
MqttClient client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());
try {
client.connect(connectOptions);
// Publish to the events or state topic based on the flag.
String subTopic = options.messageType.equals("event") ? "events" : options.messageType;
// The MQTT topic that this device will publish telemetry data to. The MQTT topic name is
// required to be in the format below. Note that this is not the same as the device registry's
// Cloud Pub/Sub topic.
String mqttTopic = String.format("/devices/%s/%s", options.deviceId, subTopic);
// Publish numMessages messages to the MQTT bridge, at a rate of 1 per second.
for (int i = 1; i <= options.numMessages; ++i) {
String payload = String.format("%s/%s-payload number-%d", options.registryId, options.deviceId, i);
System.out.format(
"Publishing %s message %d/%d: '%s'\n",
options.messageType, i, options.numMessages, payload);
// Publish "payload" to the MQTT topic. qos=1 means at least once delivery. Cloud IoT Core
// also supports qos=0 for at most once delivery.
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(1);
client.publish(mqttTopic, message);
if (options.messageType.equals("event")) {
// Send telemetry events every second
Thread.sleep(1000);
}
else {
// Note: Update Device state less frequently than with telemetry events
Thread.sleep(5000);
}
}
} finally {
// Disconnect the client and finish the run.
client.disconnect();
}
System.out.println("Finished loop successfully. Goodbye!");
// [END cloudiotcore_mqtt_publish]
}
}
and the MqttExampleOptions.java code:
package com.example.adityaprakash.test;
public class MqttExampleOptions {
String projectId;
String registryId;
String deviceId;
String privateKeyFile;
String algorithm;
String cloudRegion;
int numMessages;
String mqttBridgeHostname;
short mqttBridgePort;
String messageType;
/** Construct an MqttExampleOptions class. */
public static MqttExampleOptions values() {
try {
MqttExampleOptions res = new MqttExampleOptions();
res.projectId = "_";
res.registryId = "_";
res.deviceId = "_";
res.privateKeyFile = "com/example/adityaprakash/test/rsa_private.pem";
res.algorithm = "RS256";
res.cloudRegion = "asia-east1";
res.numMessages = 100;
res.mqttBridgeHostname = "mqtt.googleapis.com";
res.mqttBridgePort = 8883;
res.messageType = "event";
return res;
} catch (Exception e) {
System.err.println(e.getMessage());
return null;
}
}
}
Please can anyone give a solution to this problem.
P.S. I know the code looks totally crappy.I don't have experience with Android programming,so please let it go.
The example you are following is not designed for Android.
res.privateKeyFile = "com/example/adityaprakash/test/rsa_private.pem";
Will not relate to the same directory on the Android file system.
I wrote up an AndroidThings explanation of how to talk to Cloud IoT Core here: http://blog.blundellapps.co.uk/tut-google-cloud-iot-core-mqtt-on-android/
You can setup communication like this (with your pem file going into the /raw directory)
// Setup the communication with your Google IoT Core details
communicator = new IotCoreCommunicator.Builder()
.withContext(this)
.withCloudRegion("your-region") // ex: europe-west1
.withProjectId("your-project-id") // ex: supercoolproject23236
.withRegistryId("your-registry-id") // ex: my-devices
.withDeviceId("a-device-id") // ex: my-test-raspberry-pi
.withPrivateKeyRawFileId(R.raw.rsa_private)
.build();
Source code is here: https://github.com/blundell/CloudIoTCoreMQTTExample
Note that the above is good enough for a secure environment or for testing that the end to end works. However if you wanted to release a production IoT device, you would look at embedding the PEM into the ROM and using private file storage access. https://developer.android.com/training/articles/keystore.html
An example of this can be found here: https://github.com/androidthings/sensorhub-cloud-iot
Specifically this class:
https://github.com/androidthings/sensorhub-cloud-iot/blob/e50bde0100fa81818ebbadb54561b3b68ccb64b8/app/src/main/java/com/example/androidthings/sensorhub/cloud/cloudiot/MqttAuthentication.java
You can then generate and use the PEM on the device:
public Certificate getCertificate() {
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
certificate = ks.getCertificate("Cloud IoT Authentication");
if (certificate == null) {
Log.w(TAG, "No IoT Auth Certificate found, generating new cert");
generateAuthenticationKey();
certificate = ks.getCertificate(keyAlias);
}
Log.i(TAG, "loaded certificate: " + keyAlias);
}
and
private void generateAuthenticationKey() throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder("Cloud IoT Authentication",KeyProperties.PURPOSE_SIGN)
.setKeySize(2048)
.setCertificateSubject(new X500Principal("CN=unused"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build());
kpg.generateKeyPair();
}
I'm pretty sure you're not doing the file I/O correctly. Your file, "com/example/adityaprakash/test/rsa_private.pem", doesn't correspond to an actual filepath on the device. The location of files on the device may be different than in your project. You will have to determine where on the device your file actually is.
On AndroidThings, it is easier to provide the authentication credentials in an Android Resource. See my fork of the WeatherStation sample to see how this works.
First, copy the private key file (e.g. rsa_private_pkcs8) to app/src/main/res/raw/privatekey.txt
Next, you can load the key used to calculate your JWT as:
Context mContext;
int resIdPk = getResources().getIdentifier("privatekey", "raw", getPackageName());
...
InputStream privateKey = mContext.getResources().openRawResource(resIdPk);
byte[] keyBytes = inputStreamToBytes(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");
One final note, it appears that you're referencing a file that is not in pkcs8 format, which will cause issues with Java. Make sure to use a key that is packaged in PKCS8 when opening credentials on Android (Java).

Sharing IClaimsPrincipal/FedAuth Cookie between servers/apps ID1006

I have an ASP.NET app that uses Azure ACS (and indirectly ADFS) for Authentication - which all works fine. Now I've been asked to pass the SessionToken to another backend service where it can be verified and the claims extracted. [Long Story and not my choice]
I'm having fits on the decryption side, and I'm sure I'm missing something basic.
To set the stage, the error upon decryption is:
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
The ASP.NET website uses the RSA wrapper ala:
void WSFederationAuthenticationModule_OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
string thumbprint = "BDE74A3EB573297C7EE79EB980B0727D73987B0D";
X509Certificate2 certificate = GetCertificate(thumbprint);
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(certificate),
new RsaSignatureCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
(the thumbprint is the same value as added by FedUtil in web.config.
I write the token with:
if (Microsoft.IdentityModel.Web.FederatedAuthentication.SessionAuthenticationModule.TryReadSessionTokenFromCookie(out token))
{
Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler th = new Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler();
byte[] results = th.WriteToken(token);
...
which gives me:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_53382b9e-8c4b-490e-bfd5-de2e8c0f25fe-94C8D2D9079647B013081356972DE275"
xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:54bd1bd7-1110-462b-847e-7f49c1043b32</Identifier>
<Instance>urn:uuid:0462b7d7-717e-4ce2-b942-b0d6a968355b</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">AQAAANCMnd blah blah 1048 bytes total
</Cookie>
</SecurityContextToken>
and, with the same Certificate on the other box (and the token read in as a file just for testing), I have:
public static void Attempt2(FileStream fileIn, X509Certificate2 certificate, out SecurityToken theToken)
{
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaSignatureCookieTransform(certificate),
new RsaEncryptionCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
// setup
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(certificate);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
using (var reader = XmlReader.Create(fileIn))
{
theToken = sessionHandler.ReadToken(reader);
}
}
and then ReadToken throws a FormatException of
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
At this point, I can't tell if my overall approach is flawed or if I'm just missing the proverbial "one-line" that fixes all of this.
Oh, and I'm using VS2010 SP1 for the website (.NET 4.0) and I've tried both VS2010SP1 .NET 4.0 and VS2012 .NET 4.5 on the decoding side.
Thanks!
Does your app pool account for the backend service have read access to the certificate? If not give your app pool account for the backend service read access to the certificate. I had problems in the past with encryption/decryption because of this.
This might help, this will turn your FedAuth cookies into a readable XML string like:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_548a372e-1111-4df8-b610-1f9f618a5687-953155F0C35B4862A5BCE4D5D0C5ADF0" xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:c9f9b733-1111-4b01-8af3-23c8af3e19a6</Identifier>
<Instance>urn:uuid:ee955207-1111-4498-afa3-4b184e97d0be</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">long_string==</Cookie>
</SecurityContextToken>
Code:
private string FedAuthToXmlString(string fedAuthCombinedString)
{
// fedAuthCombinedString is from FedAuth + FedAuth1 cookies: just combine the strings
byte[] authBytes = Convert.FromBase64String(fedAuthCombinedString);
string decodedString = Encoding.UTF8.GetString(authBytes);
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var thumbprint = "CERT_THUMBPRINT"; // from config
var cert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false)[0];
var sessionTransforms = new List<System.IdentityModel.CookieTransform>(new System.IdentityModel.CookieTransform[]
{
new System.IdentityModel.DeflateCookieTransform(),
new System.IdentityModel.RsaSignatureCookieTransform(cert),
new System.IdentityModel.RsaEncryptionCookieTransform(cert)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(cert);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
var i = 0; // clear out invalid leading xml
while ((int)decodedString[i] != 60 && i < decodedString.Length - 1) i++; // while the first character is not <
store.Close();
return decodedString.Substring(i);
}

Resources