Skip to content

Commit

Permalink
RFC3161 timestamping of clickonce manifest and binaries (#4397)
Browse files Browse the repository at this point in the history
Currently in ClickOnce project settings, if you enter a RFC 3161 timestamp server URL, timestamping of the manifest and binaries fails. This is because the current code only works with authenthicode timestamp service.

This change will add support for RFC 3161 timestamping for ClickOnce manifests. It will first attempt an RFC 3161 timestamp and it that fails, fall back to using the older authenticode timestamping.

Reviewer: JohnHart;NingLi

Fixes AB#377113.
  • Loading branch information
sujitnayak authored and rainersigwald committed Jun 5, 2019
1 parent 9a6a350 commit 0551aa0
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 22 deletions.
30 changes: 27 additions & 3 deletions src/Tasks/ManifestUtil/SecurityUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,30 @@ private static void SignFileInternal(X509Certificate2 cert, Uri timestampUrl, st
}

private static void SignPEFile(X509Certificate2 cert, Uri timestampUrl, string path, System.Resources.ResourceManager resources, bool useSha256)
{
try
{
SignPEFileInternal(cert, timestampUrl, path, resources, useSha256, true);
}
catch(ApplicationException)
{
// error, retry with signtool /t if timestamp url was given
if (timestampUrl != null)
{
SignPEFileInternal(cert, timestampUrl, path, resources, useSha256, false);
return;
}
throw;
}
}

private static void SignPEFileInternal(X509Certificate2 cert, Uri timestampUrl,
string path, System.Resources.ResourceManager resources,
bool useSha256, bool useRFC3161Timestamp)
{
var startInfo = new ProcessStartInfo(
GetPathToTool(resources),
GetCommandLineParameters(cert.Thumbprint, timestampUrl, path, useSha256))
GetCommandLineParameters(cert.Thumbprint, timestampUrl, path, useSha256, useRFC3161Timestamp))
{
CreateNoWindow = true,
UseShellExecute = false,
Expand Down Expand Up @@ -686,7 +706,8 @@ private static void SignPEFile(X509Certificate2 cert, Uri timestampUrl, string p
}
}

internal static string GetCommandLineParameters(string certThumbprint, Uri timestampUrl, string path, bool useSha256)
internal static string GetCommandLineParameters(string certThumbprint, Uri timestampUrl, string path,
bool useSha256, bool useRFC3161Timestamp)
{
var commandLine = new StringBuilder();
if (useSha256)
Expand All @@ -700,7 +721,10 @@ internal static string GetCommandLineParameters(string certThumbprint, Uri times

if (timestampUrl != null)
{
commandLine.Append(String.Format(CultureInfo.InvariantCulture, "/t {0} ", timestampUrl.ToString()));
commandLine.Append(String.Format(CultureInfo.InvariantCulture,
"{0} {1} ",
useRFC3161Timestamp ? "/tr" : "/t",
timestampUrl.ToString()));
}
commandLine.Append(string.Format(CultureInfo.InvariantCulture, "\"{0}\"", path));
return commandLine.ToString();
Expand Down
187 changes: 168 additions & 19 deletions src/Tasks/ManifestUtil/mansign2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal static class Win32
//
// PInvoke dll's.
//
internal const String CRYPT32 = "crypt32.dll";
internal const String KERNEL32 = "kernel32.dll";
#if (true)

Expand Down Expand Up @@ -179,6 +180,65 @@ internal extern static
int _AxlPublicKeyBlobToPublicKeyToken(
[In] ref CRYPT_DATA_BLOB pCspPublicKeyBlob,
[In, Out] ref IntPtr ppwszPublicKeyToken);

// RFC3161 timestamp support

// hash algorithm OIDs
internal const string szOID_OIWSEC_sha1 = "1.3.14.3.2.26";
internal const string szOID_NIST_sha256 = "2.16.840.1.101.3.4.2.1";

[StructLayout(LayoutKind.Sequential)]
internal struct CRYPT_TIMESTAMP_CONTEXT
{
internal uint cbEncoded; // DWORD->unsigned int
internal IntPtr pbEncoded; // BYTE*
internal IntPtr pTimeStamp; // PCRYPT_TIMESTAMP_INFO->_CRYPT_TIMESTAMP_INFO*
}

[StructLayout(LayoutKind.Sequential)]
internal struct CRYPTOAPI_BLOB
{
internal uint cbData;
internal IntPtr pbData;
}

[StructLayout(LayoutKind.Sequential)]
internal struct CRYPT_TIMESTAMP_PARA
{
internal IntPtr pszTSAPolicyId;
internal bool fRequestCerts;
internal CRYPTOAPI_BLOB Nonce;
internal int cExtension;
internal IntPtr rgExtension;
}

[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport(CRYPT32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal extern static
bool CryptRetrieveTimeStamp(
[In] [MarshalAs(UnmanagedType.LPWStr)] string wszUrl,
[In] uint dwRetrievalFlags,
[In] int dwTimeout,
[In] [MarshalAs(UnmanagedType.LPStr)] string pszHashId,
[In, Out] ref CRYPT_TIMESTAMP_PARA pPara,
[In] byte[] pbData,
[In] int cbData,
[In, Out] ref IntPtr ppTsContext,
[In, Out] ref IntPtr ppTsSigner,
[In, Out] ref IntPtr phStore);

[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport(CRYPT32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
internal static extern bool CertFreeCertificateContext(IntPtr pCertContext);

[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport(CRYPT32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
internal static extern bool CertCloseStore(IntPtr pCertContext, int dwFlags);

[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport(CRYPT32, CallingConvention = CallingConvention.Winapi)]
internal static extern void CryptMemFree(IntPtr pv);
}

internal class ManifestSignedXml2 : SignedXml
Expand Down Expand Up @@ -698,7 +758,7 @@ private static void AuthenticodeSignLicenseDom(XmlDocument licenseDom, CmiManife
// Time stamp it if requested.
if (timeStampUrl != null && timeStampUrl.Length != 0)
{
TimestampSignedLicenseDom(licenseDom, timeStampUrl);
TimestampSignedLicenseDom(licenseDom, timeStampUrl, useSha256);
}

// Wrap it inside a RelData element.
Expand All @@ -707,40 +767,129 @@ private static void AuthenticodeSignLicenseDom(XmlDocument licenseDom, CmiManife
licenseDom.OuterXml + "</msrel:RelData>";
}

private static void TimestampSignedLicenseDom(XmlDocument licenseDom, string timeStampUrl)
//
// ObtainRFC3161Timestamp
//
// This function is from mage.exe in .NET FX and is used to implement RFC 3161 timestamping.
//
private static string ObtainRFC3161Timestamp(string timeStampUrl, string signatureValue, bool useSha256)
{
Win32.CRYPT_DATA_BLOB timestampBlob = new Win32.CRYPT_DATA_BLOB();
byte[] sigValueBytes = Convert.FromBase64String(signatureValue);
string timestamp = String.Empty;

string algId = useSha256 ? Win32.szOID_NIST_sha256 : Win32.szOID_OIWSEC_sha1;

unsafe
{
IntPtr ppTsContext = IntPtr.Zero;
IntPtr ppTsSigner = IntPtr.Zero;
IntPtr phStore = IntPtr.Zero;

try
{
byte[] nonce = new byte[24];

using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(nonce);
}

Win32.CRYPT_TIMESTAMP_PARA para = new Win32.CRYPT_TIMESTAMP_PARA()
{
fRequestCerts = true,
pszTSAPolicyId = IntPtr.Zero,
};

fixed (byte* pbNonce = nonce)
{
para.Nonce.cbData = (uint)nonce.Length;
para.Nonce.pbData = (IntPtr)pbNonce;

if (!Win32.CryptRetrieveTimeStamp(
timeStampUrl,
0,
60 * 1000, // 1 minute timeout
algId,
ref para,
sigValueBytes,
sigValueBytes.Length,
ref ppTsContext,
ref ppTsSigner,
ref phStore))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}

var timestampContext = (Win32.CRYPT_TIMESTAMP_CONTEXT)Marshal.PtrToStructure(ppTsContext, typeof(Win32.CRYPT_TIMESTAMP_CONTEXT));
byte[] encodedBytes = new byte[(int)timestampContext.cbEncoded];
Marshal.Copy(timestampContext.pbEncoded, encodedBytes, 0, (int)timestampContext.cbEncoded);
timestamp = Convert.ToBase64String(encodedBytes);
}
finally
{
if (ppTsContext != IntPtr.Zero)
Win32.CryptMemFree(ppTsContext);

if (ppTsSigner != IntPtr.Zero)
Win32.CertFreeCertificateContext(ppTsSigner);

if (phStore != IntPtr.Zero)
Win32.CertCloseStore(phStore, 0);
}
}

return timestamp;
}

private static void TimestampSignedLicenseDom(XmlDocument licenseDom, string timeStampUrl, bool useSha256)
{
XmlNamespaceManager nsm = new XmlNamespaceManager(licenseDom.NameTable);
nsm.AddNamespace("r", LicenseNamespaceUri);
nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
nsm.AddNamespace("as", AuthenticodeNamespaceUri);

byte[] licenseXml = Encoding.UTF8.GetBytes(licenseDom.OuterXml);
string timestamp = String.Empty;

unsafe
try
{
fixed (byte* pbLicense = licenseXml)
{
Win32.CRYPT_DATA_BLOB licenseBlob = new Win32.CRYPT_DATA_BLOB();
IntPtr pvLicense = new IntPtr(pbLicense);
licenseBlob.cbData = (uint)licenseXml.Length;
licenseBlob.pbData = pvLicense;
// Try RFC3161 first
XmlElement signatureValueNode = licenseDom.SelectSingleNode("r:license/r:issuer/ds:Signature/ds:SignatureValue", nsm) as XmlElement;
string signatureValue = signatureValueNode.InnerText;
timestamp = ObtainRFC3161Timestamp(timeStampUrl, signatureValue, useSha256);
}
// Catch CryptographicException to ensure fallback to old code (non-RFC3161)
catch (CryptographicException)
{
Win32.CRYPT_DATA_BLOB timestampBlob = new Win32.CRYPT_DATA_BLOB();

int hr = Win32.CertTimestampAuthenticodeLicense(ref licenseBlob, timeStampUrl, ref timestampBlob);
if (hr != Win32.S_OK)
byte[] licenseXml = Encoding.UTF8.GetBytes(licenseDom.OuterXml);

unsafe
{
fixed (byte* pbLicense = licenseXml)
{
throw new CryptographicException(hr);
Win32.CRYPT_DATA_BLOB licenseBlob = new Win32.CRYPT_DATA_BLOB();
IntPtr pvLicense = new IntPtr(pbLicense);
licenseBlob.cbData = (uint)licenseXml.Length;
licenseBlob.pbData = pvLicense;

int hr = Win32.CertTimestampAuthenticodeLicense(ref licenseBlob, timeStampUrl, ref timestampBlob);
if (hr != Win32.S_OK)
{
throw new CryptographicException(hr);
}
}
}
}

byte[] timestampSignature = new byte[timestampBlob.cbData];
Marshal.Copy(timestampBlob.pbData, timestampSignature, 0, timestampSignature.Length);
Win32.HeapFree(Win32.GetProcessHeap(), 0, timestampBlob.pbData);
byte[] timestampSignature = new byte[timestampBlob.cbData];
Marshal.Copy(timestampBlob.pbData, timestampSignature, 0, timestampSignature.Length);
Win32.HeapFree(Win32.GetProcessHeap(), 0, timestampBlob.pbData);
timestamp = Encoding.UTF8.GetString(timestampSignature);
}

XmlElement asTimestamp = licenseDom.CreateElement("as", "Timestamp", AuthenticodeNamespaceUri);
asTimestamp.InnerText = Encoding.UTF8.GetString(timestampSignature);
asTimestamp.InnerText = timestamp;

XmlElement dsObject = licenseDom.CreateElement("Object", SignedXml.XmlDsigNamespaceUrl);
dsObject.AppendChild(asTimestamp);
Expand Down

0 comments on commit 0551aa0

Please sign in to comment.