InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Setting the PIN of a smartcard programmatically

There are certain scenarios where it’s not feasible to require user interaction when accessing the private key of a smartcard. For example, a service does have the ability to provide a user interface. To use a key from a smartcard where a PIN is required, it must be provided to the smartcard using an alternative way. For some operations in .NET, one can use the CspParameters class to provide the PIN. But not all APIs which require smartcard access provide a way to use that class. The SSLStream is such a case. Here is a small extension method which sets the PIN for an X509Certificate2 instance:

static class X509Certificate2Extension
{
    public static void SetPinForPrivateKey(this X509Certificate2 certificate, string pin)
    {
        if (certificate == null) throw new ArgumentNullException("certificate");
        var key = (RSACryptoServiceProvider)certificate.PrivateKey;

        var providerHandle = IntPtr.Zero;
        var pinBuffer = Encoding.ASCII.GetBytes(pin);

        // provider handle is implicitly released when the certificate handle is released.
        SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle, 
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent)); SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0)); SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle)); } } internal static class SafeNativeMethods { internal enum CryptContextFlags { None = 0, Silent = 0x40 } internal enum CertificateProperty { None = 0, CryptoProviderHandle = 0x1 } internal enum CryptParameter { None = 0, KeyExchangePin = 0x20 } [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptAcquireContext( ref IntPtr hProv, string containerName, string providerName, int providerType, CryptContextFlags flags ); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool CryptSetProvParam( IntPtr hProv, CryptParameter dwParam, [In] byte[] pbData, uint dwFlags); [DllImport("CRYPT32.DLL", SetLastError = true)] internal static extern bool CertSetCertificateContextProperty( IntPtr pCertContext, CertificateProperty propertyId, uint dwFlags, IntPtr pvData ); public static void Execute(Func<bool> action) { if (!action()) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } }

The interesting piece here is, of course, the SetPinForPrivateKey method. It acquires a Win32 cryptographic context, sets the PIN and associates that pin with the certificate. Once you have an X509Certificate2 instance, you can set the PIN with just one line of code:

var certificate = GetCertificate();
certificate.SetPinForPrivateKey("123456");

The PIN is remembered until the X509Certificate2 instance is open.

Credits

This solution is taken from an stackoverflow article. I’ve just made an extension method out of it, converted all those magic numbers to enums and added a little bit of error handling.


Posted by Henning Krause on Monday, November 22, 2010 4:17 PM, last modified on Thursday, November 25, 2010 11:26 PM
Permalink | Post RSSRSS comment feed

Comments open again

I’ve just upgraded the Blogsystem and comments are now open again…


Technorati:

Posted by Henning Krause on Monday, November 22, 2010 10:10 AM, last modified on Tuesday, November 23, 2010 4:38 PM
Permalink | Post RSSRSS comment feed

Using Code Contracts Visual Studio and with Resharper

CodeContracts are a new feature to write assumptions in .NET programs. A version for .NET 3.5 is available from the Microsoft Code Contracts. With .NET 4 they are part of the BCL. Unfortunately, they do nothing out of the box – a binary rewriter is required to enable runtime checking. The rewriter is also available from the Microsoft site specified above.

Enabling Code Contracts

Once the Code Contracts are installed on a machine, they must be enabled in each Visual Studio project by specifying the CONTRACTS_FULL conditional compilation symbol in the properties of a project:

ConditionalSymbol

Next, enable the runtime checking on the Code Analysis tab:

EnableRuntimeChecking

Now you can use the Code Contracts within the project. Here is a small sample application:

using System;
using System.Diagnostics.Contracts;

namespace SampleApplication
{
    internal class Program
    {
        public static int Divide(int a, int b)
        {
            Contract.Requires(b != 0);
            Contract.Ensures(a != 0 ? Contract.Result<int>() != 0 : Contract.Result<int>() == 0);

            return a/b;
        }

        private static void Main()
        {
            Console.Out.WriteLine("Divide(4, 2) = {0}", Divide(2, 2));
            Console.Out.WriteLine("Divide(4, 0) = {0}", Divide(4, 0));
        }
    }
}

It’s just a small division method which has one pre- and one post-condition: The method requires b not to be 0. The post-condition specifies that the result of the method is 0 if a is 0 and not 0 when a is not 0. Very simple. If you run the program, it will throw an exception on the second execution of the Divide method, because b is 0 in this case:

Precondition faileed

Integration with Resharper

If you are using Resharper, you are accustomed to the null-reference checking the tool performs as you type. This is quite handy to spot null-references during design-time vs. run-time.

Here is another method which is annotated with code contracts:

 

NullReference

Notice the squiggles under the name variable and the hint to a System.NullReferenceException. If you would replace the first line of the method with a

if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");

the warning would be gone, because Resharper would sense that the code path with the if-statement is never executed if name is null.

To enabled this feature with Code Contracts, a file with certain annotations must be placed in the directory C:\Program Files\JetBrains\ReSharper\v5.1\Bin\ExternalAnnotations\mscorlib (C:\Program Files (x86)\JetBrains\ReSharper\v5.1\Bin\ExternalAnnotations\mscorlib on x64 systems). The file is attached at the end of this post.

Visual Studio Editor Code Contracts extension

A Visual Studio extension is available from the Code Contracts team. It can be downloaded via the Extension Manager. It visualizes code contracts directly in the editor.

image

Credits

The Microsoft.CodeContracts.xml is taken from a Youtrack issue http://youtrack.jetbrains.net/issue/RSRP-190566. I’ve modified the file to work with .NET 4.0.

Downloads


Posted by Henning Krause on Thursday, November 18, 2010 5:07 PM, last modified on Thursday, November 25, 2010 5:08 PM
Permalink | Post RSSRSS comment feed

Troubleshooting Smartcard error “The requested keycontainer could not be found” or “The Local Security Authority Cannot Be Contacted”

Posted under Smartcards | Comments (0)

If you use a Smartcard for cryptographic operations and get one of the errors above, the error might be in the configuration of your Smartcard Cryptographic service provider configuration. In my case I had a NetKey 3.0 card and got the error “The requested keycontainer could not be found” when accessing the private key. The error “The Local Security Authority Cannot Be Contacted” was thrown when I tried to use a certificate from the smartcard to establish a mutual authenticated SSL connection to a server.

The cause for this error was that the key length of the certificate on the smartcard was 2048 bits, but the Smartcard provider had a length of 1024 bits configured in the registry.

To fix the problem, I had to follow these steps:

  1. Open Regedit and navigate to HKEY_LOCAL_MACHINE\SOFTWARE\T-Systems\CardMiniDriverTCOS3\Applications\NKS V3.0\Key1.
  2. Set the value of the field KeySize to 2048 (if the key on your card has indeed a length of 2048 bits)
  3. Navigate to HKEY_LOCAL_MACHINE\SOFTWARE\T-Systems\CardMiniDriverTCOS3\Applications\NKS V3.0\Key2
  4. Set the value of the field KeySize to 2048.
  5. Close Regedit.
  6. Open the Card Management tool, select the smartcard and navigate to the node Smartcard Base CSP
  7. In the context menu of that node click Remove Base CSP application from the card…
    Delete
  8. Confirm the operation by entering the pin and click “Remove Base CSP application!” (seriously – who puts exclamation points on button labels?)
    image
  9. After the operation completes, close the Card management tool.
  10. Remove the smartcard from the reader and insert it again.
  11. Open the Card management tool again. This will re-initialize the card.

That’s it.


Posted by Henning Krause on Tuesday, November 16, 2010 9:04 AM, last modified on Tuesday, November 16, 2010 9:04 AM
Permalink | Post RSSRSS comment feed

Accessing Certificates on NetKey 3.0 Smartcards with Windows

Posted under Smartcards | Comments (0)

Using smartcards with Windows is not as trivial as I thought it would be. Although Microsoft ships a Smartcard provider for its CryptoAPI for current versions of Windows (an update is available for older versions of Windows), most smartcards cannot be used out of the box. Every smartcard type requires its own implementation of a Cryptographic Service Provider (see http://msdn.microsoft.com/en-us/library/ms953432.aspx for more information).

The Netkey type of smartcards is used by T-Systems in Germany. They also offer a CSP module for Windows (x86 and x64) and PKCS#11 interface libraries for Linux, MacOs and Windows. It can be downloaded from the TeleSec website.

Once the CSP is installed, the Card Management software, which comes with the driver, can be used to map certificates from a smartcard into the Windows Certificate (either the store of the current user or the local machine).

This is the main view of the TeleSec Card Management software:

Overview

Each certificate on the card has its own node. If you click on it, the certificate is displayed in the right view. From there you can import it into the store of the current user. To import the certificate into the machine store, right-click the certificate and select “Save certificate in machine store”. Administrative rights are required for the latter operation. On Windows Vista you must start the Card management software with elevated privileges.


Posted by Henning Krause on Sunday, November 14, 2010 1:11 PM, last modified on Sunday, November 14, 2010 1:52 PM
Permalink | Post RSSRSS comment feed

Workflow Foundation Part III: Participating in persistence

This is the third post about the Workflow Foundation 4. The first two posts can be found here and here.

In the second article I introduced the IncrementExtension which asynchronously increments an integer value and waits one second each time it is called. The activity used bookmarks to stop and resume the workflow while it is running. The problem is this: When the application is forcefully stopped and restarted, the index will start again at zero. This is a shame, since the workflow instance is already persisted to the database between each call.

Enter PersistenceParticipant. This is a base class for extension that which to persist data to the instance store configured for a workflow application. The PersistenceParticipant class has three virtual methods that enable the persistence functionality. Here is the IncrementExtension from the last post extended with the persistence features:

   1: using System;
   2: using System.Activities;
   3: using System.Activities.Hosting;
   4: using System.Activities.Persistence;
   5: using System.Collections.Generic;
   6: using System.Threading;
   7: using System.Xml.Linq;
   8:  
   9: namespace WorkflowConsoleApplication1
  10: {
  11:     class IncrementExtension : PersistenceParticipant, IWorkflowInstanceExtension
  12:     {
  13:         private static XName PersistencePropertyName = XNamespace.Get("urn:example.com:WorkflowDemo/1.0/Increment").GetName("LastValue");
  14:  
  15:         int _Index;
  16:         private WorkflowInstance _Instance;
  17:         Bookmark _Bookmark;
  18:  
  19:         public void Increment(Bookmark bookmark)
  20:         {
  21:             ThreadPool.QueueUserWorkItem(state =>
  22:             {
  23:                 Thread.Sleep(1000);
  24:                 var value = Interlocked.Increment(ref _Index);
  25:                 _Bookmark = bookmark;
  26:                 ContinueWorkflow(value);
  27:             }, null);
  28:         }
  29:  
  30:         private void ContinueWorkflow(int value)
  31:         {
  32:             _Instance.BeginResumeBookmark(_Bookmark, value, CompleteResume, null);
  33:         }
  34:  
  35:         public void CompleteResume(IAsyncResult ar)
  36:         {
  37:             var result = _Instance.EndResumeBookmark(ar);
  38:         }
  39:  
  40:         IEnumerable<object> IWorkflowInstanceExtension.GetAdditionalExtensions()
  41:         {
  42:             yield break;
  43:         }
  44:  
  45:         void IWorkflowInstanceExtension.SetInstance(WorkflowInstance instance)
  46:         {
  47:             _Instance = instance;
  48:         }
  49:  
  50:         protected override void CollectValues(out IDictionary<XName, object> readWriteValues, out IDictionary<XName, object> writeOnlyValues)
  51:         {
  52:             readWriteValues = new Dictionary<XName, object> 
  53:                 {
  54:                     {PersistencePropertyName, new KeyValuePair<Bookmark, int>(_Bookmark, _Index)}
  55:                 };
  56:             writeOnlyValues = null;
  57:         }
  58:  
  59:         protected override void PublishValues(IDictionary<XName, object> readWriteValues)
  60:         {
  61:             var keyvalue = (KeyValuePair<Bookmark, int>)readWriteValues[PersistencePropertyName];
  62:             _Index = (int)keyvalue.Value;
  63:             _Bookmark = (Bookmark)keyvalue.Key;
  64:  
  65:             ContinueWorkflow(_Index);
  66:         }
  67:     }
  68: }

To save data to the instance store, a unique id for the the data, represented by an XName instance. This can be created using the XNamespace class (see line 13). With this unique id, the data of the extension instance can be persisted to the instance store. To save the data the extension needs to override the CollectValues method and add the properties to be persisted to the readWriteValues (lines 50 to 56). The runtime uses the DataContractSerializer to serialize the data. Since .NET 3.5 practically every class that has the SerializableAttribute on its class and/or implements the ISerializable interface can be serialized. At minimum, this method should serialize the bookmark. This is required to continue the workflow when it’s restarted.

When the workflow is restarted the runtime calls the PublishValues method on the extension to load the instance data from the store (line 59-66). This method also should check whether to resume any bookmarks to continue the workflow.

That’s all.


Posted by Henning Krause on Wednesday, October 21, 2009 9:39 PM, last modified on Monday, November 29, 2010 7:49 PM
Permalink | Post RSSRSS comment feed

Workflow Foundation 4 – Part II: NativeActivities and Extensions

This is the second post about Workflow Foundation 4, Beta 2. This time, it’s about native activities and custom extensions. The sample used here is the same as in my last post (Persistence in Workflow Foundation 4). In that post, I used a simple workflow which incremented a integer value by one each time it was executed. To make things more interesting, the custom activity does this in an asynchronous manner.

The activity is a rather simple one, since it uses an extension to do the actual work:

   1: using System.Activities;
   2:  
   3: namespace WorkflowConsoleApplication1
   4: {
   5:     public class IncrementActivity : NativeActivity
   6:     {
   7:         public OutArgument<int> Result { get; set; }
   8:  
   9:         protected override bool CanInduceIdle { get { return true; } }
  10:  
  11:         protected override void Execute(NativeActivityContext context)
  12:         {
  13:             var extension = context.GetExtension<IncrementExtension>();
  14:  
  15:             var bookmark = context.CreateBookmark("test", IncrementCallback);
  16:             extension.Increment(bookmark);
  17:         }
  18:  
  19:         private void IncrementCallback(NativeActivityContext context, Bookmark bookmark, object value)
  20:         {
  21:             Result.Set(context, (int)value);
  22:         }
  23:     }
  24: }

The action has an out argument, the Result property. The value written to this argument is incremented by one each time the activity is called. The key to enable automatic workflow persistence are bookmarks. A short summary about bookmarks can be found in plenty of blogs (for example here). But most of them are wrong with their examples: The syntax has changed quite a bit when it comes to how to create bookmarks. When an activity calls the CreateBookmark method, the runtime automatically stops the workflow instance at that point and persists it to the instance store, if configured. The bookmark takes a name and a callback. The workflow instance is resumed, when the callback is called. In this example, the callback function sets the ‘Result’ argument to the value it receives from the extension.

Note that the activity needs to return true in the CanInduceIdle property. Otherwise, a call to CreateBookmark method will throw an exception.

Once the bookmark has been created, it is handed over to the IncrementExtension.

The IncrementExtension has one method that actually does something: The Increment method. It takes a Bookmark instance as input and resumes it after waiting for 1 second on a different thread:

   1: using System;
   2: using System.Activities;
   3: using System.Activities.Hosting;
   4: using System.Collections.Generic;
   5: using System.Threading;
   6:  
   7: namespace WorkflowConsoleApplication1
   8: {
   9:     class IncrementExtension : IWorkflowInstanceExtension
  10:     {
  11:         int _Index;
  12:         private WorkflowInstance _Instance;
  13:         Bookmark _Bookmark;
  14:  
  15:         public void Increment(Bookmark bookmark)
  16:         {
  17:             ThreadPool.QueueUserWorkItem(state =>
  18:             {
  19:                 Thread.Sleep(1000);
  20:                 var value = Interlocked.Increment(ref _Index);
  21:                 _Bookmark = bookmark;
  22:                 ContinueWorkflow(value);
  23:             }, null);
  24:         }
  25:  
  26:         private void ContinueWorkflow(int value)
  27:         {
  28:             _Instance.BeginResumeBookmark(_Bookmark, value, CompleteResume, null);
  29:         }
  30:  
  31:         public void CompleteResume(IAsyncResult ar)
  32:         {
  33:             var result = _Instance.EndResumeBookmark(ar);
  34:         }
  35:  
  36:         IEnumerable<object> IWorkflowInstanceExtension.GetAdditionalExtensions()
  37:         {
  38:             yield break;
  39:         }
  40:  
  41:         void IWorkflowInstanceExtension.SetInstance(WorkflowInstance instance)
  42:         {
  43:             _Instance = instance;
  44:         }
  45:     }
  46: }

To resume the workflow instance, the extension needs to know which instance it should resume. By default, it does not have this info. It only has an instance of a Bookmark. To get the workflow instance associated with an instance of an extension instance, the extension needs to implement the IWorkflowInstanceExtension. This method is called during the initialization of the extension instance.

Given the instance and the bookmark, the extension can now call the BeginResumeBookmark method on the workflow instance.

One question remains: Where does the extension instance come from? Simple, it is provided during the initialization of the workflow:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var app = new WorkflowApplication(new Workflow1());
   6:  
   7:         // Specify that the workflow should be persisted everytime it is idle.
   8:         app.PersistableIdle = e => PersistableIdleAction.Persist;
   9:  
  10:         // Add an instance store that is used to persist the workflow.
  11:         app.InstanceStore = new SqlWorkflowInstanceStore("Data Source=(local);Initial Catalog=WorkflowDemo;Integrated Security=True");
  12:  
  13:         app.Extensions.Add(() => new IncrementExtension());
  14:         app.Load(new Guid("1E1A5265-A135-4D42-9D64-2ECCCF22E888"));
  15:         app.Run();
  16:  
  17:         Console.WriteLine("Current Instance id: {0}", app.Id);
  18:  
  19:         Console.ReadLine();
  20:  
  21:         app.Unload();
  22:     }
  23: }

The extension is added to the workflow application in line 13. The app.Extensions.Add method either takes a singleton instance of the extension (which is then used each time the extension is requested) or a factory method which creates a new instance each time the extension is requested.


Posted by Henning Krause on Tuesday, October 20, 2009 1:24 PM, last modified on Monday, November 29, 2010 6:11 PM
Permalink | Post RSSRSS comment feed

Persistence in Workflow Foundation 4 Beta 2

The Workflow Foundation has changed dramatically since .NET 3.5… and many changes have been made even since Beta one. One of the big changes is related to persistence. In earlier versions, either an SqlWorkflowPersistenceService or the and SQLPersistenceProviderFactory was used. Things have become much easier, though, with the release of the second beta of Visual Studio 2010. A workflow can now easily be persisted with a few lines of code. For this sample, I have created a simple workflow which just counts from zero upwards. Since persistence is primarily useful for long-running workflows, I have come up with this example:

image

Obviously, this workflow runs very long… espacially, since the custom IncrementActivity waits one second before incrementing the variable ‘Index’.

To execute the workflow, only a few lines of code are required:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var app = new WorkflowApplication(new Workflow1());
   6:  
   7:         // Specify that the workflow should be persisted everytime it is idle.
   8:         app.PersistableIdle = e => PersistableIdleAction.Persist;
   9:         
  10:         // Add an instance store that is used to persist the workflow.
  11:         app.InstanceStore = new SqlWorkflowInstanceStore("Data Source=(local);Initial Catalog=WorkflowDemo;Integrated Security=True");
  12:         
  13:         app.Run();
  14:  
  15:         Console.WriteLine("Current Instance id: {0}", app.Id);
  16:         Console.ReadLine();
  17:  
  18:         app.Unload();
  19:     }
  20: }

Sure, there are simpler ways to execute a workflow but AFAIK only this method support persistence using the SqlWorkflowInstanceStore.

To reload the workflow from the database one line needs to be added to the code (in line 12, just before the app.Run() call):

   1: app.Load(new Guid("1E1A5265-A135-4D42-9D64-2ECCCF22E888"));

Of course, the id needs to be replaced with the instance id of the workflow. Once the workflow is running, the instance id can be retrieved from the app.Id property (as shown in line 15).

That’s all.

To persist workflows to a database, the necessary tables and stored procedures must be created in a database. This can easily be accomplished with two files from the .NET 4 runtime directory under %windir%\Microsoft.NET\Framework\v4.0.21006\SQL\en. The SQL statements from the two files SqlWorkflowInstanceStoreSchema.sql and SqlWorkflowInstanceStoreLogic.sql have to be executed against the target database.


Posted by Henning Krause on Monday, October 19, 2009 8:55 PM, last modified on Tuesday, October 20, 2009 1:24 PM
Permalink | Post RSSRSS comment feed

Exchange WebServices Bug with Lineendings

When you fetch the body of a mail via Exchange WebServices you might have noticed the fact that the body of the mail has its line endings, which are normally represented by a CRLF (“\r\n” in C#) replaced with a simple LF (“\n”). For the text and HTML body, this is the expected behavior. But Exchange also enforces this behavior on custom properties, which was not intended.

Below is a small program that demonstrates the problem. It creates a new item in the drafts folder of the current users mailbox and then reads the content back. You’ll notice the missing \r characters when you examine the propertyValue property.

   1: using System;
   2: using System.Linq;
   3: using System.Net;
   4: using Microsoft.Exchange.WebServices.Data;
   5:  
   6: namespace TestApplication
   7: {
   8:     internal class Program
   9:     {
  10:         private static void Main()
  11:         {
  12:             ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
  13:             var service = new ExchangeService(ExchangeVersion.Exchange2007_SP1)
  14:                           {
  15:                           Url = new Uri("https://w2k3x64/ews/exchange.asmx"),
  16:                           UseDefaultCredentials = true,
  17:                           };
  18:             Folder folder = Folder.Bind(service, WellKnownFolderName.Drafts);
  19:             var item = new PostItem(service) {Subject = "test", Body = "Line1\r\nLine2"};
  20:  
  21:             var definition = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.PublicStrings, "TestProperty",
  22:                                                             MapiPropertyType.String);
  23:             item.ExtendedProperties.Add(definition, "Line1\r\nLine2");
  24:             item.Save(folder.Id);
  25:  
  26:             ItemId id = item.Id;
  27:             item = PostItem.Bind(service, id, new PropertySet(BasePropertySet.FirstClassProperties, definition));
  28:             string propertyValue = (from property in item.ExtendedProperties 
  29:                                     where property.PropertyDefinition == definition 
  30:                                     select property.Value).First();
  31:  
  32:             Console.Out.WriteLine("item.Body = {0}", item.Body);
  33:             Console.Out.WriteLine("propertyValue = {0}", propertyValue);
  34:  
  35:             Console.ReadLine();
  36:         }
  37:     }
  38: }

So, if you are struggling with this ‘anomaly’, you need to open a case with the Microsoft products support (PSS) to get a fix.


Posted by Henning Krause on Tuesday, August 25, 2009 9:04 PM, last modified on Tuesday, August 25, 2009 9:04 PM
Permalink | Post RSSRSS comment feed

InfiniTec.DirectoryServices now on CodePlex

I’ve just created my second CodePlex project (the first one being my PushNotification Lister on http://exchangenotification.codeplex.com/). It’s the DirectoryServices.Protocols wrapper I created a while ago. The last version was still based on my old InfiniTec.Threading library which is seriously broken. The new one runs rather flawlessly and is very stable.

I will add example programs soon, I hope.


Posted by Henning Krause on Tuesday, August 11, 2009 9:14 PM, last modified on Monday, November 29, 2010 8:40 PM
Permalink | Post RSSRSS comment feed