Formatul PEM (Privacy-Enhanced Mail) este o modalitate bazată pe vechea filozofie antică textus chiorensis de-a împacheta date binare – de regulă chei criptografice, certificate X.509 sau alte asemenea artefacte criptografice – într-un fișier ușor de transmis prin canale de comunicație ce suportă exclusiv codificări ASCII.

Într-adevăr
Procedura presupune codificarea Băse64 a conținutului binar reprezentând artefactul în format ASN.1 serializat DER (Distinguished Encoding Rules) și-ncadrarea rezultatului obținut între două linii delimitatoare cu o sintaxă bine definită, cum ar fi, în cazul certificatelor (mai jos, un exemplu complet):
– drep antet, marcând începutul mesajului ––BEGIN CERTIFICATE––
– respectiv ––END CERTIFICATE–– pentru, să zicem, linia de subsol, marcând, ca atare încheierea mesajului relevant.
-----BEGIN CERTIFICATE----- MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== -----END CERTIFICATE-----
Desigur, certificatul nu este singurul bun digital ce poate fi transmis ca atare, ci și: chei publice, chei private, cereri de emitere a certificatelor (certificate requests), liste de revocare a certificatelor (certificate revocation lists) etc.
Consumarea unui asemenea format este simplă, oferindu-vă cu această ocazie și un mic utilitar C# compus din două clase, suficient de inteligent încât să ignore datele dinainte de antet și de după linia de subsol. De asemenea, tratează corect inclusiv plasarea celor trei elemente (antet – mesaj – subsol) pe o singură linie.
public class GenericPEMParser : IDisposable
{
private Stream mWorkStream;
private bool mIsDisposed = false;
private string mHeader;
private string mFooter;
private bool mOwnsStream = false;
public GenericPEMParser( Stream workStream,
string header,
string footer,
bool ownStream )
{
if (string.IsNullOrEmpty( header ))
throw new ArgumentNullException( nameof( header ) );
if (string.IsNullOrEmpty( footer ))
throw new ArgumentNullException( nameof( footer ) );
mWorkStream = workStream
?? throw new ArgumentNullException( nameof( workStream ) );
mHeader = header;
mFooter = footer;
mOwnsStream = ownStream;
}
public static GenericPEMParser PublicKeyParser( Stream workStream,
bool ownStream )
{
return new GenericPEMParser( workStream,
PEMTags.BeginPublicKey,
PEMTags.EndPublicKey,
ownStream );
}
public static GenericPEMParser CertificateParser( Stream workStream,
bool ownStream )
{
return new GenericPEMParser( workStream,
PEMTags.BeginCertificate,
PEMTags.EndCertificate,
ownStream );
}
public byte [] ReadObject()
{
StringBuilder buffer =
new StringBuilder();
mWorkStream.Seek( 0, SeekOrigin.Begin );
using (StreamReader rdr = new StreamReader( mWorkStream,
Encoding.ASCII,
detectEncodingFromByteOrderMarks: true,
bufferSize: 256,
leaveOpen: true ))
{
string line = rdr.ReadLine();
bool startCollecting = false;
StringComparison comparison = StringComparison
.InvariantCultureIgnoreCase;
while (line != null)
{
line = line.Trim();
startCollecting = startCollecting
|| line.StartsWith( mHeader, comparison );
if (line.EndsWith( mFooter, comparison ))
break;
if (startCollecting)
{
line = line.Replace( mHeader, "", comparison );
line = line.Replace( mFooter, "", comparison );
if (!string.IsNullOrWhiteSpace( line ))
buffer.Append( line );
}
line = rdr.ReadLine();
}
}
return Convert.FromBase64String( buffer.ToString() );
}
protected virtual void Dispose( bool disposing )
{
if (!mIsDisposed)
{
if (disposing)
{
if (mOwnsStream)
mWorkStream?.Dispose();
mWorkStream = null;
}
mIsDisposed = true;
}
}
public void Dispose()
{
Dispose( true );
}
}
Parametrul ownStream determină dacă în metoda .Dispose() se gestionează și fluxul de date workStream (valoarea true va determina apelarea .Dispose() și pentru workStream; valorea false îl va lăsa-n boii lui și pe răspunderea apelantului).
În continuare, clasa PEMTags, unde-am definit marcatorii relevanți (pentru mine) ai antetului, respectiv subsolului:
public static class PEMTags
{
public const string BeginPublicKey
= "-----BEGIN PUBLIC KEY-----";
public const string EndPublicKey
= "-----END PUBLIC KEY-----";
public const string BeginCertificate
= "-----BEGIN CERTIFICATE-----";
public const string EndCertificate
= "-----END CERTIFICATE-----";
}
În sfârșit, un exemplu de utilizare ce va tipări datele obținute în format HEX-ASCII:
public static void RunPEMTryOuts()
{
byte [] certPemData = Encoding.ASCII
.GetBytes( GetPEMCertificateText() );
using (MemoryStream memoryStream =
new MemoryStream( certPemData ))
using (GenericPEMParser parser =
GenericPEMParser.CertificateParser( memoryStream,
ownStream: false ))
{
byte [] certData = parser.ReadObject();
string certHexAscii = BitConverter
.ToString( certData )
.Replace( "-", "" );
Console.WriteLine( certHexAscii );
}
}
Dacă veți copia ce se afișează pe ecran și ulterior veți introduce aici, veți putea vedea și structura ASN.1 a certificatului:

Exemplu certificat deserializat