//*************************************************************************** // AuthAttr // // Copyright (C) 2003. Michel I. Gallant // //*************************************************************************** // // AuthAttr.cs // // This C# utility for .NET Framework 1.0/1.1 // - Extracts all embedded certificates and displays basic cert info. // - Extracts Authenticated Attributes if a CMS/PKCS #7 signed message blob. // - Decodes the SMIMECapabilities attribute(1.2.840.113549.1.9.15), if present. //**************************************************************************** using System; using System.IO; using System.Runtime.InteropServices; using System.ComponentModel; using System.Reflection; using System.Text; using System.Security.Cryptography.X509Certificates; namespace JavaScience { public class Win32 { [DllImport("crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)] public static extern IntPtr CertOpenSystemStore( IntPtr hCryptProv, string storename) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertCloseStore( IntPtr hCertStore, uint dwFlags) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern IntPtr CryptMsgOpenToDecode( uint dwMsgEncodingType, uint dwFlags, uint dwMsgType, IntPtr hCryptProv, IntPtr pRecipientInfo, IntPtr pStreamInfo ); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CryptMsgUpdate( IntPtr hCryptMsg, byte[] pbData, int cbData, bool fFinal ); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CryptMsgClose( IntPtr hCryptMsg ); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CryptMsgGetParam( IntPtr hCryptMsg, uint dwParamType, int dwIndex, IntPtr pvData, ref uint pcbData ); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CryptMsgGetParam( IntPtr hCryptMsg, uint dwParamType, int dwIndex, [In, Out] byte[] pvData, ref int pcbData ); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CryptMsgGetParam( IntPtr hCryptMsg, uint dwParamType, int dwIndex, ref uint pvData, ref uint pcbData ); [DllImport("crypt32.dll")] public static extern bool CryptDecodeObject( uint CertEncodingType, uint lpszStructType, byte[] pbEncoded, int cbEncoded, uint flags, IntPtr pvStructInfo, ref int cbStructInfo ); [DllImport("crypt32.dll", SetLastError=true)] public static extern IntPtr CryptFindOIDInfo( uint dwKeyType, [MarshalAs(UnmanagedType.LPStr)] String szOID, uint dwGroupId ); } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct CERT_NAME_INFO { public int cRDN; public IntPtr rgRDN; //PCERT_RDN } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct CERT_RDN { public int cRDNAttr; public IntPtr rgRDNAttr; } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct CERT_RDN_ATTR { public IntPtr pszObjId; public int dwValueType; public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_ALGORITHM_IDENTIFIER{ [MarshalAs(UnmanagedType.LPStr)] public String pszObjID; public uint cbData; //CRYPT_OBJID_BLOB Parameters blob public IntPtr pbData; //CRYPT_OBJID_BLOB Parameters blob } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_RC2_CBC_PARAMETERS { public int dwVersion; public bool fIV; [MarshalAs(UnmanagedType.ByValArray, SizeConst=8)] public byte[] rgbIV; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_ATTRIBUTES { public int cAttr; public IntPtr rgAttr; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_ATTRIBUTE { [MarshalAs(UnmanagedType.LPStr)] public String pszObjId; public int cValue; public IntPtr rgValue; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_ATTR_BLOB { public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_INT_BLOB { public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_SMIME_CAPABILITIES { public int cCapability; public IntPtr rgCapability; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_SMIME_CAPABILITY { [MarshalAs(UnmanagedType.LPStr)] public String pszObjId; public int cbData; public IntPtr pbData; } public class AuthAttr { const uint PKCS_7_ASN_ENCODING = 0x00010000; const uint X509_ASN_ENCODING = 0x00000001; const uint CMSG_ENVELOPE_ALGORITHM_PARAM = 15; const uint CMSG_RECIPIENT_COUNT_PARAM = 17; const uint CMSG_RECIPIENT_INFO_PARAM = 19; const uint CMSG_CERT_COUNT_PARAM = 11; const uint CMSG_CERT_PARAM = 12; const uint PKCS_RC2_CBC_PARAMETERS = 41; const uint X509_NAME = 7; const uint CMSG_SIGNER_AUTH_ATTR_PARAM = 9; const uint PKCS_SMIME_CAPABILITIES = 42; const uint X509_INTEGER = 27; const uint X509_MULTI_BYTE_INTEGER =28; const uint CRYPT_OID_INFO_OID_KEY = 1; const int CERT_RDN_PRINTABLE_STRING = 4; const int CERT_RDN_TELETEX_STRING = 5; const int CERT_RDN_T61_STRING = 5; const int CERT_RDN_IA5_STRING = 7; const int CERT_RDN_GENERAL_STRING = 10; const int CERT_RDN_UNICODE_STRING = 12; const int CRYPT_RC2_40BIT_VERSION = 160; const int CRYPT_RC2_56BIT_VERSION = 52; const int CRYPT_RC2_64BIT_VERSION = 120; const int CRYPT_RC2_128BIT_VERSION = 58; const string szOID_RSA_RC2CBC = "1.2.840.113549.3.2"; const string szOID_RSA_RC4 = "1.2.840.113549.3.4"; const string szOID_RSA_DES_EDE3_CBC = "1.2.840.113549.3.7"; const string szOID_RSA_RC5_CBCPad = "1.2.840.113549.3.9"; const string szOID_RSA_SMIMECapabilities = "1.2.840.113549.1.9.15"; static uint MY_ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING ; public static void Main(String[] args){ String filename = null; IntPtr hMsg = IntPtr.Zero; IntPtr pProvInfo = IntPtr.Zero; IntPtr pAuthAttrib = IntPtr.Zero; IntPtr paramData = IntPtr.Zero; uint attribBytecount = 0; uint certscount = 0; uint cbint = 4; int cbcert = 0; if(args.Length < 1){ Usage(); return; } filename = args[0]; if(!File.Exists(filename)){ Console.WriteLine("File '{0}' not found. ", filename); return; } //---------- Get encoded enveloped data from file ---------- byte[] envdata = GetFileBytes(filename); Console.WriteLine("File '{0}' ({1} bytes)", filename, envdata.Length); hMsg = Win32.CryptMsgOpenToDecode(MY_ENCODING_TYPE, 0, 0, pProvInfo, IntPtr.Zero, IntPtr.Zero); if(hMsg == IntPtr.Zero){ Console.WriteLine("\nCouldn't open message to decode"); showWin32Error(Marshal.GetLastWin32Error()); return; } if(!Win32.CryptMsgUpdate(hMsg, envdata, envdata.Length, true)){ //message may be b64 encoded if(hMsg != IntPtr.Zero) Win32.CryptMsgClose(hMsg) ; hMsg = Win32.CryptMsgOpenToDecode(MY_ENCODING_TYPE, 0, 0, pProvInfo, IntPtr.Zero, IntPtr.Zero); string b64str = (new ASCIIEncoding()).GetString(envdata); // possibly b64 file envdata = Convert.FromBase64String(b64str); if(!Win32.CryptMsgUpdate(hMsg, envdata, envdata.Length, true)){ Win32.CryptMsgClose(hMsg) ; Console.WriteLine("\nCouldn't update message to decode (probably invalid data)"); showWin32Error(Marshal.GetLastWin32Error());; return; } Console.WriteLine("Base64 encoded enveloped data") ; } //-------------- Get embedded certificates ------------------- if(Win32.CryptMsgGetParam(hMsg, CMSG_CERT_COUNT_PARAM, 0, ref certscount, ref cbint)) Console.WriteLine("------ Embedded certificates: {0} -------------", certscount); byte[] bincert; for (int j=0; j0) { byte[] pdata = new byte[cbData]; Marshal.Copy(sCap.pbData, pdata, 0, cbData); if( capOID.Equals(szOID_RSA_RC2CBC)) Console.Write("({0} bits)", CAPParam(pdata)); } pCap = (IntPtr) (4 + 2*IntPtr.Size + (int)pCap); //walk IntPtr to next capabilities struct } Marshal.FreeHGlobal(pSMIMECaps) ; Console.WriteLine("") ; } } pAttrib = (IntPtr) (4 + 2*IntPtr.Size + (int)pAttrib) ; //walk IntPtr to next attribute struct } Marshal.FreeHGlobal(pAuthAttrib); if(hMsg != IntPtr.Zero) Win32.CryptMsgClose(hMsg) ; } //------ get friendly name for OID ------- private static string OIDName(String OID){ IntPtr oidinfo = Win32.CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, OID, 0) ; if(oidinfo == IntPtr.Zero) return null; IntPtr offsetoidname = (IntPtr)((int)oidinfo + 4 + IntPtr.Size); //3rd member of CRYPT_OID_INFO IntPtr pOIDname = Marshal.ReadIntPtr(offsetoidname, 0) ; string oidname = Marshal.PtrToStringUni(pOIDname); return oidname; } //----- Get CRYPT_SMIME_CAPABILITY Parameters ---------- //----- NOTE: This approach for decoding may not be robust! ---- private static int CAPParam(byte[] encodedparam) { IntPtr structinfo = IntPtr.Zero; int cbstruct = 0; int paramsize = 0; if(Win32.CryptDecodeObject(MY_ENCODING_TYPE, X509_MULTI_BYTE_INTEGER, encodedparam, encodedparam.Length, 0, IntPtr.Zero, ref cbstruct)) { structinfo = Marshal.AllocHGlobal(cbstruct); Win32.CryptDecodeObject(MY_ENCODING_TYPE, X509_MULTI_BYTE_INTEGER, encodedparam, encodedparam.Length, 0, structinfo, ref cbstruct); CRYPT_INT_BLOB parmint = (CRYPT_INT_BLOB) Marshal.PtrToStructure(structinfo, typeof(CRYPT_INT_BLOB)) ; int cblob = parmint.cbData; if(cblob>4) //at most 4 bytes should be available return paramsize; byte[] intblob = new byte[4]; Marshal.Copy(parmint.pbData, intblob, 0, cblob) ; try{ paramsize = BitConverter.ToInt32(intblob, 0); } catch(Exception) {;} finally{ Marshal.FreeHGlobal(structinfo); } } return paramsize; } private static void showCertInfo(X509Certificate x509cert) { Console.WriteLine("Name: " + x509cert.GetName()); Console.WriteLine("Issuer: " + x509cert.GetIssuerName()); Console.WriteLine("Serial Number: " + x509cert.GetSerialNumberString()); Console.WriteLine("Expiration Date: " + x509cert.GetExpirationDateString()); //Console.WriteLine("PublicKey: " + x509cert.GetPublicKeyString()); } private static byte[] GetFileBytes(String filename){ if(!File.Exists(filename)) return null; Stream stream=new FileStream(filename,FileMode.Open); int datalen = (int)stream.Length; byte[] filebytes =new byte[datalen]; stream.Seek(0,SeekOrigin.Begin); stream.Read(filebytes,0,datalen); stream.Close(); return filebytes; } private static void DisplayBytes(byte[] data) { for(int i=1; i<=data.Length; i++){ Console.Write("{0:x2} ", data[i-1]) ; if(i%16 == 0) Console.WriteLine(""); } Console.WriteLine(); } private static void Usage() { Console.WriteLine("\nUsage:\nAuthAttr "); } private static void showWin32Error(int errorcode){ Win32Exception myEx=new Win32Exception(errorcode); Console.WriteLine("Error message: {0} (Code: 0x{1:X})", myEx.Message, myEx.ErrorCode); } } }