mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-23 12:30:50 +01:00
CA-349836: Reworked handling of manifest and signature for appliance import/export.
- Moved the code to the ExportApplianceAction so it's easier to modify the action's description. - Fixed issue where the user's option for adding signature/manifest to a plain OVF package was ignored. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
This commit is contained in:
parent
28c0f1d62a
commit
e325548a75
@ -33,6 +33,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using XenAdmin.Core;
|
||||
using XenAdmin.Network;
|
||||
@ -75,6 +76,8 @@ namespace XenAdmin.Actions.OvfActions
|
||||
m_eulas = eulas;
|
||||
m_signAppliance = signAppliance;
|
||||
m_createManifest = createManifest;
|
||||
if (m_signAppliance && m_certificate == null)
|
||||
throw new ArgumentNullException(nameof(m_certificate));
|
||||
m_certificate = certificate;
|
||||
m_encryptFiles = encryptFiles;
|
||||
m_encryptPassword = encryptPassword;
|
||||
@ -134,7 +137,6 @@ namespace XenAdmin.Actions.OvfActions
|
||||
|
||||
CheckForCancellation();
|
||||
var ovfPath = Path.Combine(appFolder, appFile);
|
||||
Description = String.Format(Messages.CREATING_FILE, appFile);
|
||||
OVF.SaveAs(env, ovfPath);
|
||||
PercentComplete = 85;
|
||||
|
||||
@ -142,12 +144,12 @@ namespace XenAdmin.Actions.OvfActions
|
||||
|
||||
if (m_createOVA)
|
||||
{
|
||||
log.Info($"Archiving OVF package {m_applianceFileName} into OVA");
|
||||
Description = String.Format(Messages.CREATING_FILE, String.Format("{0}.ova", m_applianceFileName));
|
||||
|
||||
ManifestAndSign(appFolder, appFile);
|
||||
ManifestAndSign(env, appFolder, appFile);
|
||||
PercentComplete = 90;
|
||||
|
||||
log.Info($"Archiving OVF package {m_applianceFileName} into OVA");
|
||||
Description = string.Format(Messages.CREATING_OVA_FILE, string.Format("{0}.ova", m_applianceFileName));
|
||||
|
||||
try
|
||||
{
|
||||
OVF.ConvertOVFtoOVA(env, ovfPath, () => Cancelling, m_compressOVFfiles);
|
||||
@ -172,25 +174,110 @@ namespace XenAdmin.Actions.OvfActions
|
||||
}
|
||||
|
||||
PercentComplete = 95;
|
||||
ManifestAndSign(appFolder, appFile);
|
||||
ManifestAndSign(env, appFolder, appFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
ManifestAndSign(env, appFolder, appFile);
|
||||
}
|
||||
|
||||
PercentComplete = 100;
|
||||
Description = Messages.COMPLETED;
|
||||
}
|
||||
|
||||
private void ManifestAndSign(string appFolder, string appFile)
|
||||
private void ManifestAndSign(EnvelopeType ovfEnv, string appFolder, string appFile)
|
||||
{
|
||||
if (m_signAppliance || m_createManifest)
|
||||
Manifest(ovfEnv, appFolder, appFile);
|
||||
|
||||
if (m_signAppliance)
|
||||
{
|
||||
Description = Messages.SIGNING_APPLIANCE;
|
||||
OVF.Sign(m_certificate, appFolder, appFile);
|
||||
}
|
||||
else if (m_createManifest)
|
||||
Sign(m_certificate, appFolder, appFile);
|
||||
}
|
||||
|
||||
private void Manifest(EnvelopeType ovfEnv, string pathToOvf, string ovfFileName)
|
||||
{
|
||||
var manifestFiles = new List<string> {ovfFileName};
|
||||
|
||||
var files = ovfEnv?.References?.File;
|
||||
if (files != null)
|
||||
manifestFiles.AddRange(files.Select(f => f.href).ToList());
|
||||
|
||||
var fileDigests = new List<FileDigest>();
|
||||
foreach (var mf in manifestFiles)
|
||||
{
|
||||
Description = Messages.CREATING_MANIFEST;
|
||||
OVF.Manifest(appFolder, appFile);
|
||||
CheckForCancellation();
|
||||
|
||||
var mfPath = Path.Combine(pathToOvf, mf);
|
||||
if (!File.Exists(mfPath))
|
||||
continue;
|
||||
|
||||
Description = string.Format(Messages.CREATING_MANIFEST_CHECKSUM, mf);
|
||||
log.Info($"Calculating checksum for file {mf}");
|
||||
|
||||
using (FileStream stream = new FileStream(mfPath, FileMode.Open, FileAccess.Read))
|
||||
using (var hasher = HashAlgorithm.Create(FileDigest.DEFAULT_HASHING_ALGORITHM))
|
||||
{
|
||||
var hash = hasher?.ComputeHash(stream);
|
||||
fileDigests.Add(new FileDigest(Path.GetFileName(mf), hash));
|
||||
}
|
||||
}
|
||||
|
||||
CheckForCancellation();
|
||||
Description = Messages.CREATING_MANIFEST;
|
||||
string manifest = Path.Combine(pathToOvf, $"{Path.GetFileNameWithoutExtension(ovfFileName)}{Package.MANIFEST_EXT}");
|
||||
|
||||
using (var stream = new FileStream(manifest, FileMode.Create, FileAccess.Write))
|
||||
using (var sw = new StreamWriter(stream))
|
||||
{
|
||||
foreach (var fileDigest in fileDigests)
|
||||
sw.WriteLine(fileDigest.ToManifestLine());
|
||||
sw.Flush();
|
||||
}
|
||||
|
||||
log.Info($"Created manifest for package {ovfFileName}");
|
||||
}
|
||||
|
||||
private void Sign(X509Certificate2 certificate, string pathToOvf, string ovfFileName)
|
||||
{
|
||||
Description = Messages.SIGNING_APPLIANCE;
|
||||
|
||||
var packageName = Path.GetFileNameWithoutExtension(ovfFileName);
|
||||
string manifestFileName = packageName + Package.MANIFEST_EXT;
|
||||
string manifestPath = Path.Combine(pathToOvf, manifestFileName);
|
||||
|
||||
CheckForCancellation();
|
||||
|
||||
byte[] signedHash = null;
|
||||
using (FileStream stream = new FileStream(manifestPath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var hasher = HashAlgorithm.Create(FileDigest.DEFAULT_HASHING_ALGORITHM))
|
||||
{
|
||||
var hash = hasher?.ComputeHash(stream);
|
||||
if (hash != null)
|
||||
{
|
||||
using (var csp = (RSACryptoServiceProvider)certificate.PrivateKey)
|
||||
signedHash = csp.SignHash(hash, CryptoConfig.MapNameToOID(FileDigest.DEFAULT_HASHING_ALGORITHM));
|
||||
}
|
||||
}
|
||||
|
||||
var fileDigest = new FileDigest(manifestFileName, signedHash);
|
||||
string signatureFileName = packageName + Package.CERTIFICATE_EXT;
|
||||
string signaturePath = Path.Combine(pathToOvf, signatureFileName);
|
||||
|
||||
using (FileStream stream = new FileStream(signaturePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
using (StreamWriter writer = new StreamWriter(stream))
|
||||
{
|
||||
writer.WriteLine(fileDigest.ToManifestLine());
|
||||
|
||||
// Export the certificate encoded in Base64 using DER
|
||||
string b64Cert = Convert.ToBase64String(certificate.Export(X509ContentType.SerializedCert));
|
||||
|
||||
writer.WriteLine("-----BEGIN CERTIFICATE-----");
|
||||
writer.WriteLine(b64Cert);
|
||||
writer.WriteLine("-----END CERTIFICATE-----");
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
log.Info($"Digitally signed package {ovfFileName}");
|
||||
}
|
||||
|
||||
protected override void CleanOnError()
|
||||
|
@ -78,36 +78,40 @@ namespace XenAdmin.Actions.OvfActions
|
||||
|
||||
protected override void RunCore()
|
||||
{
|
||||
// The appliance has a signature and the user asked to verify it.
|
||||
if (m_verifySignature)
|
||||
{
|
||||
Description = Messages.VERIFYING_SIGNATURE;
|
||||
|
||||
try
|
||||
{
|
||||
// The appliance is known to have a signature and the user asked to verify it.
|
||||
m_package.VerifySignature();
|
||||
|
||||
// If the appliance has a signature, then it has a manifest.
|
||||
// Always verify the manifest after verifying the signature.
|
||||
m_package.VerifyManifest();
|
||||
log.Info($"Verified signature for package {m_package.Name}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"Signature verification failed for package {m_package.Name}", e);
|
||||
throw new Exception(String.Format(Messages.VERIFYING_SIGNATURE_ERROR, e.Message));
|
||||
}
|
||||
}
|
||||
else if (m_verifyManifest)
|
||||
|
||||
// The appliance has
|
||||
// - a signature (in which case it also has a manifest that should be verified AFTER the signature); or
|
||||
// - a manifest without a signature
|
||||
// and the user asked to verify it
|
||||
|
||||
if (m_verifySignature || m_verifyManifest)
|
||||
{
|
||||
Description = Messages.VERIFYING_MANIFEST;
|
||||
|
||||
try
|
||||
{
|
||||
// The appliance had a manifest without a signature and the user asked to verify it.
|
||||
// VerifyManifest() throws an exception when verification fails for any reason.
|
||||
m_package.VerifyManifest();
|
||||
log.Info($"Verified manifest for package {m_package.Name}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"Manifest verification failed for package {m_package.Name}", e);
|
||||
throw new Exception(String.Format(Messages.VERIFYING_MANIFEST_ERROR, e.Message));
|
||||
}
|
||||
}
|
||||
|
27
XenModel/Messages.Designer.cs
generated
27
XenModel/Messages.Designer.cs
generated
@ -11080,15 +11080,6 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Archiving appliance files into single package file '{0}'....
|
||||
/// </summary>
|
||||
public static string CREATING_FILE {
|
||||
get {
|
||||
return ResourceManager.GetString("CREATING_FILE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Creating manifest file....
|
||||
/// </summary>
|
||||
@ -11098,6 +11089,15 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Calculating checksum for file '{0}'....
|
||||
/// </summary>
|
||||
public static string CREATING_MANIFEST_CHECKSUM {
|
||||
get {
|
||||
return ResourceManager.GetString("CREATING_MANIFEST_CHECKSUM", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Creating new pool '{0}' with master '{1}'.
|
||||
/// </summary>
|
||||
@ -11125,6 +11125,15 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Archiving appliance files into single package file '{0}'....
|
||||
/// </summary>
|
||||
public static string CREATING_OVA_FILE {
|
||||
get {
|
||||
return ResourceManager.GetString("CREATING_OVA_FILE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Creating vApp '{0}'....
|
||||
/// </summary>
|
||||
|
@ -3955,12 +3955,12 @@ For optimal performance and reliability during VM migration, ensure that the net
|
||||
<data name="CREATING_DISKS" xml:space="preserve">
|
||||
<value>Creating disks</value>
|
||||
</data>
|
||||
<data name="CREATING_FILE" xml:space="preserve">
|
||||
<value>Archiving appliance files into single package file '{0}'...</value>
|
||||
</data>
|
||||
<data name="CREATING_MANIFEST" xml:space="preserve">
|
||||
<value>Creating manifest file...</value>
|
||||
</data>
|
||||
<data name="CREATING_MANIFEST_CHECKSUM" xml:space="preserve">
|
||||
<value>Calculating checksum for file '{0}'...</value>
|
||||
</data>
|
||||
<data name="CREATING_NAMED_POOL_WITH_MASTER" xml:space="preserve">
|
||||
<value>Creating new pool '{0}' with master '{1}'</value>
|
||||
</data>
|
||||
@ -3970,6 +3970,9 @@ For optimal performance and reliability during VM migration, ensure that the net
|
||||
<data name="CREATING_NEW_FOLDERS" xml:space="preserve">
|
||||
<value>Creating new folders...</value>
|
||||
</data>
|
||||
<data name="CREATING_OVA_FILE" xml:space="preserve">
|
||||
<value>Archiving appliance files into single package file '{0}'...</value>
|
||||
</data>
|
||||
<data name="CREATING_VMSS" xml:space="preserve">
|
||||
<value>Creating snapshot schedule '{0}'...</value>
|
||||
</data>
|
||||
|
@ -49,6 +49,8 @@ namespace XenOvf
|
||||
/// </summary>
|
||||
public class FileDigest
|
||||
{
|
||||
public const string DEFAULT_HASHING_ALGORITHM = "SHA256";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance from a line in the manifest.
|
||||
/// </summary>
|
||||
@ -69,6 +71,14 @@ namespace XenOvf
|
||||
Digest = ToArray(DigestAsString);
|
||||
}
|
||||
|
||||
public FileDigest(string fileName, byte[] digest, string hashingAlgorithm = DEFAULT_HASHING_ALGORITHM)
|
||||
{
|
||||
AlgorithmName = hashingAlgorithm;
|
||||
Name = fileName;
|
||||
Digest = digest;
|
||||
DigestAsString = Digest == null ? "" : string.Join("", Digest.Select(b => $"{b:x2}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the file with the digest.
|
||||
/// </summary>
|
||||
@ -83,6 +93,11 @@ namespace XenOvf
|
||||
|
||||
public byte[] Digest{ get; }
|
||||
|
||||
public string ToManifestLine()
|
||||
{
|
||||
return $"{AlgorithmName}({Name})= {DigestAsString}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a hex string to a binary array.
|
||||
/// </summary>
|
||||
@ -271,12 +286,12 @@ namespace XenOvf
|
||||
{
|
||||
_DescriptorFileName = _archiveIterator.CurrentFileName();
|
||||
}
|
||||
else if (string.Compare(extension, Properties.Settings.Default.manifestFileExtension, true) == 0)
|
||||
else if (string.Compare(extension, MANIFEST_EXT, true) == 0)
|
||||
{
|
||||
if (_DescriptorFileName == null || string.Compare(_archiveIterator.CurrentFileName(), ManifestFileName, true) != 0)
|
||||
continue;
|
||||
}
|
||||
else if (string.Compare(extension, Properties.Settings.Default.certificateFileExtension, true) == 0)
|
||||
else if (string.Compare(extension, CERTIFICATE_EXT, true) == 0)
|
||||
{
|
||||
if (_DescriptorFileName == null || string.Compare(_archiveIterator.CurrentFileName(), CertificateFileName, true) != 0)
|
||||
continue;
|
||||
@ -357,8 +372,8 @@ namespace XenOvf
|
||||
while (_archiveIterator.HasNext())
|
||||
{
|
||||
var extension = Path.GetExtension(_archiveIterator.CurrentFileName());
|
||||
if (string.Compare(extension, Properties.Settings.Default.manifestFileExtension, true) == 0 ||
|
||||
string.Compare(extension, Properties.Settings.Default.certificateFileExtension, true) == 0)
|
||||
if (string.Compare(extension, MANIFEST_EXT, true) == 0 ||
|
||||
string.Compare(extension, CERTIFICATE_EXT, true) == 0)
|
||||
continue;
|
||||
|
||||
var fileDigest = manifest.Find(fd => string.Compare(fd.Name, _archiveIterator.CurrentFileName(), true) == 0);
|
||||
@ -417,6 +432,9 @@ namespace XenOvf
|
||||
{
|
||||
protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public const string MANIFEST_EXT = ".mf";
|
||||
public const string CERTIFICATE_EXT = ".cert";
|
||||
|
||||
// Cache these properties because they are expensive to get
|
||||
private string _descriptorXml;
|
||||
private byte[] _RawManifest;
|
||||
@ -449,12 +467,12 @@ namespace XenOvf
|
||||
/// <summary>
|
||||
/// According to the OVF specification, base name of the manifest file must be the same as the descriptor file.
|
||||
/// </summary>
|
||||
protected string ManifestFileName => Name + Properties.Settings.Default.manifestFileExtension;
|
||||
protected string ManifestFileName => Name + MANIFEST_EXT;
|
||||
|
||||
/// <summary>
|
||||
/// According to the OVF specification, base name of the certificate file must be the same as the descriptor file.
|
||||
/// </summary>
|
||||
protected string CertificateFileName => Name + Properties.Settings.Default.certificateFileExtension;
|
||||
protected string CertificateFileName => Name + CERTIFICATE_EXT;
|
||||
|
||||
/// <summary>
|
||||
/// Contents of the OVF file.
|
||||
@ -551,6 +569,7 @@ namespace XenOvf
|
||||
return RawCertificate != null;
|
||||
}
|
||||
|
||||
/// <exception cref="Exception">Thrown when verification fails for any reason</exception>>
|
||||
public void VerifySignature()
|
||||
{
|
||||
using (var certificate = new X509Certificate2(RawCertificate))
|
||||
@ -594,6 +613,7 @@ namespace XenOvf
|
||||
|
||||
protected abstract string ReadAllText(string fileName);
|
||||
|
||||
/// <exception cref="Exception">Thrown when verification fails for any reason</exception>>
|
||||
public abstract void VerifyManifest();
|
||||
|
||||
public abstract bool HasFile(string fileName);
|
||||
|
29
XenOvfApi/Properties/Settings.Designer.cs
generated
29
XenOvfApi/Properties/Settings.Designer.cs
generated
@ -12,7 +12,7 @@ namespace XenOvf.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
@ -330,33 +330,6 @@ namespace XenOvf.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute(".mf")]
|
||||
public string manifestFileExtension {
|
||||
get {
|
||||
return ((string)(this["manifestFileExtension"]));
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute(".cert")]
|
||||
public string certificateFileExtension {
|
||||
get {
|
||||
return ((string)(this["certificateFileExtension"]));
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("SHA1")]
|
||||
public string securityAlgorithm {
|
||||
get {
|
||||
return ((string)(this["securityAlgorithm"]));
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("Schemas\\secext-1.0.xsd")]
|
||||
|
@ -104,15 +104,6 @@
|
||||
<Setting Name="knownFileExtensions" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">.vhd,.pvp,.vmdk,.mf,.cert,.xva,.ovf,.wim,.vdi,.sdi,.iso,.gz</Value>
|
||||
</Setting>
|
||||
<Setting Name="manifestFileExtension" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">.mf</Value>
|
||||
</Setting>
|
||||
<Setting Name="certificateFileExtension" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">.cert</Value>
|
||||
</Setting>
|
||||
<Setting Name="securityAlgorithm" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">SHA1</Value>
|
||||
</Setting>
|
||||
<Setting Name="wsseSchemaLocation" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">Schemas\secext-1.0.xsd</Value>
|
||||
</Setting>
|
||||
|
@ -397,167 +397,6 @@ namespace XenOvf
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SIGNATURES
|
||||
/// <summary>
|
||||
/// Create a Manifest for the OVF
|
||||
/// </summary>
|
||||
/// <param name="pathToOvf">Absolute path to the OVF files</param>
|
||||
/// <param name="ovfFileName">OVF file name (file.ovf)</param>
|
||||
public static void Manifest(string pathToOvf, string ovfFileName)
|
||||
{
|
||||
List<ManifestFileEntry> mfes = new List<ManifestFileEntry>();
|
||||
SHA1 sha1 = SHA1.Create();
|
||||
EnvelopeType ovfenv;
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream stream = new FileStream(Path.Combine(pathToOvf, ovfFileName), FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
ManifestFileEntry mfe = new ManifestFileEntry();
|
||||
mfe.Algorithm = Properties.Settings.Default.securityAlgorithm;
|
||||
mfe.Filename = ovfFileName;
|
||||
mfe.Digest = sha1.ComputeHash(stream);
|
||||
mfes.Add(mfe);
|
||||
stream.Position = 0;
|
||||
|
||||
using (StreamReader sr = new StreamReader(stream))
|
||||
ovfenv = Tools.Deserialize<EnvelopeType>(sr.ReadToEnd());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.ErrorFormat("OVF.Security.Manifest: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (ovfenv != null && ovfenv.References != null && ovfenv.References.File != null && ovfenv.References.File.Length > 0)
|
||||
{
|
||||
File_Type[] files = ovfenv.References.File;
|
||||
|
||||
foreach (File_Type file in files)
|
||||
{
|
||||
string currentfile = Path.Combine(pathToOvf, file.href);
|
||||
if (!File.Exists(currentfile))
|
||||
continue;
|
||||
|
||||
ManifestFileEntry mfe = new ManifestFileEntry();
|
||||
|
||||
using (FileStream computestream = new FileStream(currentfile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
mfe.Algorithm = Properties.Settings.Default.securityAlgorithm;
|
||||
mfe.Filename = file.href;
|
||||
mfe.Digest = sha1.ComputeHash(computestream);
|
||||
mfes.Add(mfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string manifest = Path.Combine(pathToOvf, string.Format("{0}{1}", Path.GetFileNameWithoutExtension(ovfFileName), Properties.Settings.Default.manifestFileExtension));
|
||||
|
||||
File.Delete(manifest); //no exception is thrown if file does not exist, so no need to check
|
||||
|
||||
using (FileStream stream = new FileStream(manifest, FileMode.CreateNew, FileAccess.Write))
|
||||
{
|
||||
using (StreamWriter sw = new StreamWriter(stream))
|
||||
{
|
||||
foreach (ManifestFileEntry mf in mfes)
|
||||
sw.WriteLine(mf.ToString());
|
||||
|
||||
sw.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("OVF.Manifest completed");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Digitaly Sign the OVF
|
||||
/// </summary>
|
||||
/// <param name="x509">Signing Certificate</param>
|
||||
/// <param name="pathToOvf">Absolute path to the OVF files</param>
|
||||
/// <param name="ovfFileName">OVF file name (file.ovf)</param>
|
||||
public static void Sign(X509Certificate2 Certificate, string PackageFolder, string PackageFileName)
|
||||
{
|
||||
if (Certificate == null)
|
||||
{
|
||||
throw new ArgumentException(Messages.CERTIFICATE_IS_INVALID);
|
||||
}
|
||||
|
||||
string PackageName = PackageNameFromFileName(PackageFileName);
|
||||
|
||||
string ManifestPath = Path.Combine(PackageFolder, PackageName) + Properties.Settings.Default.manifestFileExtension;
|
||||
|
||||
// Create the manifest if it doesn't exist.
|
||||
if (!File.Exists(ManifestPath))
|
||||
{
|
||||
Manifest(PackageFolder, PackageFileName);
|
||||
}
|
||||
|
||||
// Compute the SHA1 hash of the manifest.
|
||||
byte[] hash = null;
|
||||
|
||||
using (FileStream stream = new FileStream(ManifestPath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
hash = sha1.ComputeHash(stream);
|
||||
}
|
||||
|
||||
// Describe the file to sign.
|
||||
ManifestFileEntry signed = new ManifestFileEntry();
|
||||
signed.Algorithm = Properties.Settings.Default.securityAlgorithm;
|
||||
signed.Filename = Path.GetFileName(ManifestPath);
|
||||
|
||||
// Compute the signature.
|
||||
try
|
||||
{
|
||||
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)Certificate.PrivateKey;
|
||||
|
||||
signed.Digest = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
string message = exception.Message;
|
||||
}
|
||||
|
||||
// Create the signature file.
|
||||
string SignaturePath = Path.Combine(PackageFolder, PackageName) + Properties.Settings.Default.certificateFileExtension;
|
||||
|
||||
if (File.Exists(SignaturePath))
|
||||
File.Delete(SignaturePath);
|
||||
|
||||
using (FileStream stream = new FileStream(SignaturePath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||
using (StreamWriter writer = new StreamWriter(stream))
|
||||
{
|
||||
// Describe the signed file.
|
||||
writer.WriteLine(signed.ToString());
|
||||
|
||||
// Export the certificate encoded in Base64 using DER.
|
||||
writer.WriteLine("-----BEGIN CERTIFICATE-----");
|
||||
string b64Cert = Convert.ToBase64String(Certificate.Export(X509ContentType.SerializedCert));
|
||||
writer.WriteLine(b64Cert);
|
||||
writer.WriteLine("-----END CERTIFICATE-----");
|
||||
writer.WriteLine("\r\n");
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string PackageNameFromFileName(string FileName)
|
||||
{
|
||||
// Always drop the last extension.
|
||||
string PackageName = Path.GetFileNameWithoutExtension(FileName);
|
||||
|
||||
if (Path.HasExtension(PackageName) && Path.GetExtension(PackageName).ToLower().EndsWith("ova"))
|
||||
{
|
||||
// Drop any .ova extension.
|
||||
PackageName = Path.GetFileNameWithoutExtension(PackageName);
|
||||
}
|
||||
|
||||
return PackageName;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PRIVATE
|
||||
private static void CryptoFileWrapper(EnvelopeType env, string ovffilename, string password, bool encrypt)
|
||||
{
|
||||
@ -1006,31 +845,5 @@ namespace XenOvf
|
||||
iv[loop - key.Length] = result[loop];
|
||||
}
|
||||
#endregion
|
||||
|
||||
internal class ManifestFileEntry
|
||||
{
|
||||
public string Algorithm = null;
|
||||
public string Filename = null;
|
||||
public byte[] Digest = null;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Algorithm != null && Filename != null && Digest != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in Digest)
|
||||
{
|
||||
sb.AppendFormat("{0:x2}", b);
|
||||
}
|
||||
// same as:
|
||||
//string bc = BitConverter.ToString(Digest).Replace("-","");;
|
||||
return string.Format("{0}({1})= {2}", Algorithm, Filename, sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException("NULL data inside ManifestFileEntry");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ namespace XenOvf
|
||||
foreach (File_Type file in files)
|
||||
{
|
||||
string ext = Path.GetExtension(file.href).ToLower();
|
||||
if (ext == Properties.Settings.Default.manifestFileExtension || ext == Properties.Settings.Default.certificateFileExtension)
|
||||
if (ext == Package.MANIFEST_EXT || ext == Package.CERTIFICATE_EXT)
|
||||
continue;
|
||||
|
||||
if (package.HasFile(file.href))
|
||||
|
@ -109,15 +109,6 @@
|
||||
<setting name="knownFileExtensions" serializeAs="String">
|
||||
<value>.vhd,.pvp,.vmdk,.mf,.cert,.xva,.ovf,.wim,.vdi,.sdi,.iso,.gz</value>
|
||||
</setting>
|
||||
<setting name="manifestFileExtension" serializeAs="String">
|
||||
<value>.mf</value>
|
||||
</setting>
|
||||
<setting name="certificateFileExtension" serializeAs="String">
|
||||
<value>.cert</value>
|
||||
</setting>
|
||||
<setting name="securityAlgorithm" serializeAs="String">
|
||||
<value>SHA1</value>
|
||||
</setting>
|
||||
<setting name="wsseSchemaLocation" serializeAs="String">
|
||||
<value>Schemas\secext-1.0.xsd</value>
|
||||
</setting>
|
||||
@ -229,7 +220,8 @@
|
||||
</setting>
|
||||
<setting name="knownVirtualSystemTypes" serializeAs="Xml">
|
||||
<value>
|
||||
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<string>xen-3.0-unknown</string>
|
||||
<string>xen-3.0-x32</string>
|
||||
<string>xen-3.0-x86</string>
|
||||
|
Loading…
Reference in New Issue
Block a user