Using X.509 keys as OpenPGP keys in .NET

As of version 1.7.7.3 of DidiSoft OpenPGP Library for .NET, X.509 certificates and private keys can be used transparently as OpenPGP keys.

In this chapter we are going to demonstrate how to use them directly from files located in the file system or from the local Windows storage.

Chapter table of contents

1. Using a X.509 certificate/private key located in a file

2. Importing a X.509 certificate/private key in a DidiSoft KeyStore object

3. Obtaining a X.509 from the Windows certificate store

4. Compatibility

1. Using a X.509 certificate/private key located in a file

We can use a X.509 certificate (.cer, .p7b) for encryption and signature verification just as a normal public OpenPGP key. Below is shown how to encrypt a file using a X.509 certificate file:

C# example

using System;
using System.IO;
using DidiSoft.Pgp;
 
public class EncryptWithX509
{
 public static void Demo()
 {
  using (Stream publicKey = File.OpenRead(@"DataFiles\X509\public.cer"))
  using (Stream data = File.OpenRead(@"DataFiles\INPUT.txt"))
  using (Stream output = File.Create(@"DataFiles\OUTPUT.pgp"))
  {
    // is the output ASCII or binary            
    bool asciiArmor = true;
 
    // create an instance of the library
    PGPLib pgp = new PGPLib();
 
    pgp.EncryptStream(data, publicKey, output, asciiArmor);
  }
 }
}

VB.NET example

Imports System
Imports System.IO
Imports DidiSoft.Pgp
 
Public Class EncryptWithX509
 Public Shared Sub Demo()
  Using publicKey As Stream = File.OpenRead("DataFiles\X509\public.cer")
   Using data As Stream = File.OpenRead("DataFiles\INPUT.txt")
    Using output As Stream = File.Create("DataFiles\OUTPUT.pgp")
	 ' is the output ASCII or binary            
	 Dim asciiArmor As Boolean = True
 
	 ' create an instance of the library
	 Dim pgp As New PGPLib()
 
	 pgp.EncryptStream(data, publicKey, output, asciiArmor)
    End Using
   End Using
  End Using
 End Sub
End Class

The same way we can use its corresponding X.509 private key (.pfx, .p12) for decrypting and OpenPGP digital signing.

C# example

using System;
using System.IO;
using DidiSoft.Pgp;
 
public class DecryptX509
{
 public static void Demo()
 {
  // create an instance of the library
  PGPLib pgp = new PGPLib();
 
  using (Stream secKey = File.OpenRead(@"DataFiles\X509\private_key.p12"))
  using (Stream encrypted = File.OpenRead(@"DataFiles\OUTPUT.pgp"))
  using (Stream decrypted = File.Create(@"DataFiles\OUTPUT.txt"))
  {
	string keyPassword = "changeit";
	pgp.DecryptStream(encrypted, secKey, keyPassword, decrypted);
  }
 }
}

VB.NET example

Imports System
Imports System.IO
Imports DidiSoft.Pgp
 
Public Class DecryptX509
 Public Shared Sub Demo()
  ' create an instance of the library
  Dim pgp As New PGPLib()
 
  Using secKey As Stream = File.OpenRead("DataFiles\X509\private_key.p12")
   Using encrypted As Stream = File.OpenRead("DataFiles\OUTPUT.pgp")
    Using decrypted As Stream = File.Create("DataFiles\OUTPUT.txt")
	Dim keyPassword As String = "changeit"
	pgp.DecryptStream(encrypted, secKey, keyPassword, decrypted)
    End Using
   End Using
  End Using
 End Sub
End Class

2. Importing a X.509 certificate in a DidiSoft KeyStore object

The DidiSoft.Pgp.KeyStore object also supports X.509 keys. When imported they are wrapped as OpenPGP keys.

The User Id of such keys is of the form: CN=aaa, OU=bbb, O=ccc, L=ddd, ST=eee, C=fff

Example importing a X.509 certificate:

DidiSoft.Pgp.KeyStore ks = new DidiSoft.Pgp.KeyStore();            
DidiSoft.Pgp.KeyPairInformation key = ks.ImportPublicKey(@"C:\Test\X509_public_key.p7b");

Example importing a X.509 private key:

DidiSoft.Pgp.KeyStore ks = new DidiSoft.Pgp.KeyStore();            
DidiSoft.Pgp.KeyPairInformation key = ks.ImportPrivateKey(@"C:\Test\X509_private_key.pfx", "password");

Please note, that when we import a X.509 private key, we have to use the KeyStore.ImportPrivateKey method that accepts a key password as a second parameter in order to be able to decode the RSA asymmetric cipher parameters.

3. Obtaining a X.509 from the Windows certificate store

We can also use X.509 certificates and keys directly from the Windows certificate store.

Please note that in order to use X.509 private key components from the Windows certificate store, they must have been imported there as ‘exportable’.

This sample shows how to import a private key and a public key (certificate). In a production code, you will probably check the certificate.Subject in order to provide the correct password:

C# example

using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using DidiSoft.Pgp;
 
public class ImportX509WinStore
{
 public static void Demo()
 {
  // instance of the DidiSoft KeyStore (in-memory)
  KeyStore ks = KeyStore.OpenInMemory();
 
  X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
  store.Open(OpenFlags.ReadOnly);
 
  System.Collections.IEnumerator enumCerts = store.Certificates.GetEnumerator();
  while (enumCerts.MoveNext())
  {
   Object o = enumCerts.Current;
   if (o is X509Certificate2)
   {
	X509Certificate2 certificate = o as X509Certificate2;
	if (certificate.HasPrivateKey)
	{
	 string keyPassword = "password";
	 byte[] data = certificate.Export(X509ContentType.Pkcs12, keyPassword);
	 ks.ImportPrivateKey(new MemoryStream(data), keyPassword);
	}
	else
	{
	 byte[] data = certificate.RawData;
	 ks.ImportPublicKey(new MemoryStream(data));
	}
   }
  }
 }
}

VB.NET example

Imports System
Imports System.IO
Imports System.Security.Cryptography.X509Certificates
Imports DidiSoft.Pgp
 
Public Class ImportX509WinStore
 Public Shared Sub Demo()
  ' instance of the DidiSoft KeyStore (in-memory)
  Dim ks As KeyStore = KeyStore.OpenInMemory()
 
  Dim store As New X509Store(StoreName.My, StoreLocation.CurrentUser)
  store.Open(OpenFlags.[ReadOnly])
 
  Dim enumCerts As System.Collections.IEnumerator = store.Certificates.GetEnumerator()
  While enumCerts.MoveNext()
   Dim o As [Object] = enumCerts.Current
   If TypeOf o Is X509Certificate2 Then
	Dim certificate As X509Certificate2 = TryCast(o, X509Certificate2)
	If certificate.HasPrivateKey Then
		Dim keyPassword As String = "password"
		Dim data As Byte() = certificate.Export(X509ContentType.Pkcs12, keyPassword)
		ks.ImportPrivateKey(New MemoryStream(data), keyPassword)
	Else
		Dim data As Byte() = certificate.RawData
		ks.ImportPublicKey(New MemoryStream(data))
	End If
   End If
  End While
 End Sub
End Class

4. Compatibility

The usage of X.509 certificates from OpenPGP is a common task that has already been addressed by Symantec PGP and GnuPG (GpgSm). Unfortunately their implementations are not compatible with each other.

At the moment our implementation is compatible with the above implementations only when decrypting OpenPGP archives created by them. Unfortunately when encrypting we are not compatible with both of them.

We are working on the compatibility issue, and this chapter will be updated in our next release when it is achieved.

Summary

This chapter illustrated how to use X.509 certificates transparently as OpenPGP keys. The keys can be used from files or streams obtained either from files or from the Windows certificate store