Issue with the mentalis security library on Azure

Oct 3, 2011 at 11:52 PM

When running in the Azure emulator, the SSL library that AE.Net.Mail uses (courtesy of mentalis.org) isn't able to establish a secure connection with imap.gmail.com - due to a failure in certificate processing (SSLException -  "The pulic key should be at least 512 bits").  The same code works perfectly running on Win7 64-bit.

Digging around, the following thread describes the problem (offset bug when making a call to the native crypto API's on Windows Server), and offers a workaround.  http://www.mentalis.org/forum/thread.qpx/949

The workaround gets rid of the certificate processing issue, but even though the connection is established, the IMAP handshake doesn't complete (the client hangs on a ReadLine() from the socket). 

Again, this only seems to occur under the Azure emulator (and presumably on Windows Server and Windows Azure proper, although I have to verify this).

Anyone else run into this issue?

 

Oct 4, 2011 at 7:04 AM

I ran down this issue.  It is a little bit complex (required hours of reverse-engineering memory structures to figure out what was going on), so bear with me.

The mentalis libraries are pretty old and really have no chance of working on the newer 64-bit server OS's.  There are a few pointer arithmetic issues that needed to be fixed.

Credit goes to the thread I mentioned above for getting me on the right path... but the solution listed in that thread doesn't actually work and will typically result in an AV taking down the process (for reasons I won't expand on here).

The biggest issue appears to be that the CERT_INFO struct in the Windows Crypto API appears to use architecture-dependent WORDs for it's integer fields.  So on 32-bit architectures these lay out in memory as 32 bit ints, whereas on 64-bit architectures they lay out as 64 bit longs. The problem is that the CertificateInfo struct (defined in SecurityStructures.cs) uses the "int" datatype which is always a 32-bit integer.  Note that the pointers in the CertificateInfo struct are actually fine since they are correctly defined as IntPtr's.

There are three changes I made in order to get this narrow scenario to work - i.e. enabling the AE Imap libraries to work on sizeof(IntPtr) == 8 bytes systems (i.e. Windows Server, Windows Azure):

1.  SecurityStructures.cs: create a shadow structure to CertificateInfo which actually uses IntPtr's instead of int's for the integer fields.  This will result in a correct mapping between the in-memory structures in the underlying native Crypto libraries, and the shadow structure.

2.  Certificate.cs: in InitCertificate(), marshall the CERT_INFO struct into the shadow structure, and then create an instance of the original structure, and copy all the fields over (taking care to cast the things that are supposed to be integers but are now IntPtr's back into Int32's).

3.  Certificate.cs: in GetPublicKeyLength(), check to see if this is a 64-bit platform, and if so, use 96 instead of 56 as the offset for the field we are trying to pass into the CertGetPublicKeyLength() native SSPI call.

Note - the mentalis security library is quite large and chances are high that there are other similar issues lurking in the code.  This fix is really only for the use case I described above - getting the AE Imap library to work on 64-bit Windows server systems.

The changes are provided below:

1.

    /// <summary>
    /// The CertificateInfo structure contains a certificate's information.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    internal struct OG_CertificateInfo
    { //CERT_INFO
        // added by Omri Gazitt 2011-10-03
        // on 64-bit systems, the "int" fields below actually show up as 64-bit values in memory. 
        // on 32-bit systems, they show up as 32-bit integers.  therefore, make them all IntPtr's
        // so that they can be the proper size in each platform.
        // we use this struct to load the cert info using the Windows crypto API's.
        // then we copy all the values to the original struct above - so that the rest of the 
        // library can remain unmodified.

        //public int dwVersion;
        public IntPtr dwVersion;
        //public int SerialNumbercbData;
        public IntPtr SerialNumbercbData;
        public IntPtr SerialNumberpbData; // BYTE*
        public IntPtr SignatureAlgorithmpszObjId; // LPSTR
        //public int SignatureAlgorithmParameterscbData;
        public IntPtr SignatureAlgorithmParameterscbData;
        public IntPtr SignatureAlgorithmParameterspbData; // BYTE*
        //public int IssuercbData;
        public IntPtr IssuercbData;
        public IntPtr IssuerpbData; // BYTE*
        public long NotBefore; // FILETIME
        public long NotAfter; // FILETIME
        //public int SubjectcbData;
        public IntPtr SubjectcbData;
        public IntPtr SubjectpbData; // BYTE*
        public IntPtr SubjectPublicKeyInfoAlgorithmpszObjId; // LPSTR
        //public int SubjectPublicKeyInfoAlgorithmParameterscbData;
        public IntPtr SubjectPublicKeyInfoAlgorithmParameterscbData;
        public IntPtr SubjectPublicKeyInfoAlgorithmParameterspbData; // BYTE*
        //public int SubjectPublicKeyInfoPublicKeycbData;
        public IntPtr SubjectPublicKeyInfoPublicKeycbData;
        public IntPtr SubjectPublicKeyInfoPublicKeypbData; // BYTE*
        //public int SubjectPublicKeyInfoPublicKeycUnusedBits;
        public IntPtr SubjectPublicKeyInfoPublicKeycUnusedBits;
        //public int IssuerUniqueIdcbData;
        public IntPtr IssuerUniqueIdcbData;
        public IntPtr IssuerUniqueIdpbData; // BYTE*
        //public int IssuerUniqueIdcUnusedBits;
        public IntPtr IssuerUniqueIdcUnusedBits;
        //public int SubjectUniqueIdcbData;
        public IntPtr SubjectUniqueIdcbData;
        public IntPtr SubjectUniqueIdpbData; // BYTE*
        //public int SubjectUniqueIdcUnusedBits;
        public IntPtr SubjectUniqueIdcUnusedBits;
        //public int cExtension;
        public IntPtr cExtension;
        public IntPtr rgExtension; // /PCERT_EXTENSION/
    }

2.

		private void InitCertificate(IntPtr handle, bool duplicate, CertificateStore store) {
			if (handle == IntPtr.Zero)
				throw new ArgumentException("Invalid certificate handle!");
			if (duplicate)
				m_Handle = SspiProvider.CertDuplicateCertificateContext(handle);
			else
				m_Handle = handle;
			m_Context = (CertificateContext)Marshal.PtrToStructure(handle, typeof(CertificateContext));

            // Omri Gazitt: workaround 64-bit systems, 2011-10-03
            // removed the line below
            //m_CertInfo = (CertificateInfo)Marshal.PtrToStructure(m_Context.pCertInfo, typeof(CertificateInfo));
            // added the lines below
            var certInfo = (OG_CertificateInfo)Marshal.PtrToStructure(m_Context.pCertInfo, typeof(OG_CertificateInfo));
            m_CertInfo = new CertificateInfo()
            {
                dwVersion = certInfo.dwVersion.ToInt32(),
                SerialNumbercbData = certInfo.SerialNumbercbData.ToInt32(),
                SerialNumberpbData = certInfo.SerialNumberpbData,
                SignatureAlgorithmpszObjId = certInfo.SignatureAlgorithmpszObjId,
                SignatureAlgorithmParameterscbData = certInfo.SignatureAlgorithmParameterscbData.ToInt32(),
                SignatureAlgorithmParameterspbData = certInfo.SignatureAlgorithmParameterspbData,
                IssuercbData = certInfo.IssuercbData.ToInt32(),
                IssuerpbData = certInfo.IssuerpbData,
                NotBefore = certInfo.NotBefore,
                NotAfter = certInfo.NotAfter,
                SubjectcbData = certInfo.SubjectcbData.ToInt32(),
                SubjectpbData = certInfo.SubjectpbData,
                SubjectPublicKeyInfoAlgorithmpszObjId = certInfo.SubjectPublicKeyInfoAlgorithmpszObjId,
                SubjectPublicKeyInfoAlgorithmParameterscbData = certInfo.SubjectPublicKeyInfoAlgorithmParameterscbData.ToInt32(),
                SubjectPublicKeyInfoAlgorithmParameterspbData = certInfo.SubjectPublicKeyInfoAlgorithmParameterspbData,
                SubjectPublicKeyInfoPublicKeycbData = certInfo.SubjectPublicKeyInfoPublicKeycbData.ToInt32(),
                SubjectPublicKeyInfoPublicKeypbData = certInfo.SubjectPublicKeyInfoPublicKeypbData,
                SubjectPublicKeyInfoPublicKeycUnusedBits = certInfo.SubjectPublicKeyInfoPublicKeycUnusedBits.ToInt32(),
                IssuerUniqueIdcbData = certInfo.IssuerUniqueIdcbData.ToInt32(),
                IssuerUniqueIdpbData = certInfo.IssuerUniqueIdpbData,
                IssuerUniqueIdcUnusedBits = certInfo.IssuerUniqueIdcUnusedBits.ToInt32(),
                SubjectUniqueIdcbData = certInfo.SubjectUniqueIdcbData.ToInt32(),
                SubjectUniqueIdpbData = certInfo.SubjectUniqueIdpbData,
                SubjectUniqueIdcUnusedBits = certInfo.SubjectUniqueIdcUnusedBits.ToInt32(),
                cExtension = certInfo.cExtension.ToInt32(),
                rgExtension = certInfo.rgExtension
            };
            // end changes OG 2011-10-03

            if (store == null) {
				m_Store = null;
			} else {
				m_Store = store;
			}
		}

 3.

		public int GetPublicKeyLength() {
            // Omri Gazitt - removed the line below and replaced it with the if/else statements - 2011-10-03
            // The offset that we're looking for is for the CertificateInfo.SubjectPublicKeyInfoAlgorithmpszObjId field.  
            // For 32-bit IntPtr architectures this will reside at offset decimal 56.  For 64-bit IntPtr architectures this will reside at 
            // offset decimal 96.  
            //return SspiProvider.CertGetPublicKeyLength(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, new IntPtr(m_Context.pCertInfo.ToInt64() + 56));
            if (System.Runtime.InteropServices.Marshal.SizeOf(new IntPtr()) == 8)
                return SspiProvider.CertGetPublicKeyLength(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, new IntPtr(m_Context.pCertInfo.ToInt64() + 96));
            else
                return SspiProvider.CertGetPublicKeyLength(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, new IntPtr(m_Context.pCertInfo.ToInt64() + 56));
        }

 I hope this helps someone :-)

 

Coordinator
Oct 24, 2011 at 5:34 PM

Wow.  Thanks for your work on this.  I've been running on 64-bit architecture but hadn't seen this issue.  I've just pushed a fix, but instead of adding a new struct, I changed the existing one.  Let me know if this works for you (http://aenetmail.codeplex.com/SourceControl/changeset/changes/a615336e461c).

Thanks again!

Nov 16, 2011 at 8:19 PM

Thanks Andy - glad to see that it was useful and that you were able to integrate it back into your source.  (Yeah, the original code also worked for me on my 64-bit Win7 laptop, but failed on Azure - so there is likely some difference in .NET execution model between the desktop and server OS environments that was contributing here).

Also glad to see that you moved the source to github!

Thanks,

Omri.