sign.cpp
/*****************************************************************************
*
* File: Sign.cpp
*
* Description:
* This sample is a simple application that might be used as a starting-point
* for an application that uses the Packaging API. This sample demonstrates
* signature generation and validation using a sample signing policy described
* in Sign.h
*
* ------------------------------------
*
* This file is part of the Microsoft Windows SDK Code Samples.
*
* Copyright (C) Microsoft Corporation. All rights reserved.
*
* This source code is intended only as a supplement to Microsoft
* Development Tools and/or on-line documentation. See these other
* materials for detailed information regarding Microsoft code samples.
*
* THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
*
****************************************************************************/
#include <stdio.h>
#include <windows.h>
#include <shlobj.h>
#include <msopc.h> // For OPC APIs
#include <WinCrypt.h> // For certificate stuff
#include <CryptDlg.h> // For certificate selection dialog
#include <strsafe.h>
#include <new>
#include "Util.h"
#include "Sign.h"
#include "urlmon.h" // for Uri stuff
// Input/Output file names.
static const WCHAR g_unsignedFilePath[] = L"SampleMusicBundle.zip";
const WCHAR g_signedFilePath[] = L"SampleMusicBundle_signed.zip";
static const WCHAR g_signatureMethod[] = L"http://www.w3.org/2000/09/xmldsig#rsa-sha1";
static const WCHAR g_defaultDigestMethod[] = L"http://www.w3.org/2000/09/xmldsig#sha1";
static const WCHAR g_signatureOriginPartName[] = L"/package/services/digital-signature/origin.psdsor";
// Content types for parts in music bundle.
static const WCHAR g_trackContentType[] = L"audio/x-ms-wma";
static const WCHAR g_lyricContentType[] = L"text/plain";
static const WCHAR g_albumArtContentType[] = L"image/jpeg";
static const WCHAR g_tracklistPartContentType[] = L"application/vnd.ms-wpl";
// Relationship types for relationships in music bundle.
static const WCHAR g_tracklistRelationshipType[] =
L"http://schemas.example.com/package/2008/relationships/media-bundle/tracklist";
// The relationship used to target the album art part is the thumbnail
// relationship that is defined in the OPC (ECMA-376 1st edition Part 2).
static const WCHAR g_albumArtRelationshipType[] =
L"https://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail";
static const WCHAR g_albumWebsiteRelationshipType[] =
L"http://schemas.example.com/package/2008/relationships/media-bundle/album-website";
static LPCWSTR g_trackRelationshipType =
L"http://schemas.example.com/package/2008/relationships/media-bundle/playlist-song";
static LPCWSTR g_lyricRelationshipType =
L"http://schemas.example.com/package/2008/relationships/media-bundle/song-lryic";
// Signature relationship type as defined in OPC spec.
static LPCWSTR g_signatureRelationshipType =
L"https://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature";
// Get the parts that need to be signed according to the signing policy.
// This method may return valid values for some of the out parameters even
// when the method fails. The caller must free resources if a non-NULL
// value is returned.
//
// Parameters:
// opcPackage - the package of the parts
// trackParts - when the function returns successfully, it holds the array of track parts.
// lyricParts - when the function returns successfully, it holds the array of lyric parts.
// tracklistPart - when the function returns successfully, it holds the tracklist part.
// albumArtPart - when the function returns successfully, it holds the album art part.
HRESULT
GetPartsToBeSigned(
IOpcPackage* opcPackage,
OpcPartArray* trackParts,
OpcPartArray* lyricParts,
IOpcPart** tracklistPart,
IOpcPart** albumArtPart
)
{
IOpcPartSet * partSet = NULL;
IOpcRelationshipSet * relsSet = NULL;
IOpcRelationshipSet * partRelsSet = NULL;
IOpcRelationshipEnumerator * partRelsEnumerator = NULL;
UINT32 count = 0;
BOOL hasNext = FALSE;
BOOL hasPrevious = FALSE;
HRESULT hr = opcPackage->GetRelationshipSet(&relsSet);
if (SUCCEEDED(hr))
{
hr = opcPackage->GetPartSet(&partSet);
}
if (SUCCEEDED(hr))
{
// Get album art part. According to the sample signing policy,
// this part needs to be signed.
hr = GetPartByRelationshipType(
partSet,
relsSet,
g_albumArtRelationshipType,
g_albumArtContentType,
albumArtPart
);
}
// Get tracklist part; as defined by the sample signing policy, this part
// needs to be signed.
if (SUCCEEDED(hr))
{
// Get tracklist part; as defined by the sample signing policy, this
// part needs to be signed.
hr = GetPartByRelationshipType(
partSet,
relsSet,
g_tracklistRelationshipType,
g_tracklistPartContentType,
tracklistPart
);
}
if (SUCCEEDED(hr))
{
// Get the set relationships that have the tracklist part as
// their source.
hr = (*tracklistPart)->GetRelationshipSet(&partRelsSet);
}
if (SUCCEEDED(hr))
{
// Get the relationships of the track relationship type from
// the set. These relationships have the tracklist part as their
// source and a track part as their target.
hr = partRelsSet->GetEnumeratorForType(g_trackRelationshipType, &partRelsEnumerator);
}
// Count the number of relationships in the set that target track parts.
// This count is the number of track parts in the music bundle.
// Note: The number of track parts and lyric parts are assumed to be equal.
while (
SUCCEEDED(hr) &&
SUCCEEDED(hr = partRelsEnumerator->MoveNext(&hasNext)) &&
hasNext
)
{
count++;
}
// Initialize the OpcPartArrays with the number of track parts in the
// music bundle.
if (SUCCEEDED(hr))
{
hr = trackParts->Init(count);
}
if (SUCCEEDED(hr))
{
hr = lyricParts->Init(count);
}
// There is no method to reset the current position of the enumerator
// to its starting position. Instead, there are two ways to rest an
// enumerator to its starting position:
// 1. Iterate backwards through the enumerator until the starting
// position is reached.
// 2. Clone the enumerator BEFORE iterating and when a reset is
// desired, iterate through the clone, which will still be in
// its starting position.
while (
SUCCEEDED(hr) &&
SUCCEEDED(hr = partRelsEnumerator->MovePrevious(&hasPrevious)) &&
hasPrevious
)
{
IOpcRelationship * rels = NULL;
IOpcPart * trackPart = NULL;
IOpcRelationshipSet * trackPartRelationshipSet = NULL;
IOpcPart * lyricPart = NULL;
hr = partRelsEnumerator->GetCurrent(&rels);
if (SUCCEEDED(hr))
{
// Get track part; as defined by the sample signing policy, this part
// needs to be signed.
hr = GetRelationshipTargetPart(partSet, rels, g_trackContentType, &trackPart);
}
if (SUCCEEDED(hr))
{
// Track part was retrieved; therefore, add it to the array of track
// parts to be signed.
hr = trackParts->Append(trackPart);
}
if (SUCCEEDED(hr))
{
// Get the relationships that have this track part as their source.
hr = trackPart->GetRelationshipSet(&trackPartRelationshipSet);
}
if (SUCCEEDED(hr))
{
// Get lyric part; as defined by the sample signing policy, this part
// needs to be signed.
hr = GetPartByRelationshipType(
partSet,
trackPartRelationshipSet,
g_lyricRelationshipType,
g_lyricContentType,
&lyricPart
);
}
if (SUCCEEDED(hr))
{
// Lyric part was retrieved; therefore, add it to the array of lyric
// parts to be signed.
hr = lyricParts->Append(lyricPart);
}
// Release resources
if (rels)
{
rels->Release();
rels = NULL;
}
if (trackPart)
{
trackPart->Release();
trackPart = NULL;
}
if (trackPartRelationshipSet)
{
trackPartRelationshipSet->Release();
trackPartRelationshipSet = NULL;
}
if (lyricPart)
{
lyricPart->Release();
lyricPart = NULL;
}
}
// Release resources
if (partSet)
{
partSet->Release();
partSet = NULL;
}
if (relsSet)
{
relsSet->Release();
relsSet = NULL;
}
if (partRelsSet)
{
partRelsSet->Release();
partRelsSet = NULL;
}
if (partRelsEnumerator)
{
partRelsEnumerator->Release();
partRelsEnumerator = NULL;
}
// Note: If the package format specification has specific requirements
// for some parts (such as size or content type), the package should be
// validated against those requirements before it is signed. For
// example, if the specification requires that a specific part never be
// empty, that requirement should be validated by checking whether the part
// size is zero.
return hr;
}
// Adds a reference to a Relationships part that contains relationships that will
// be selected for signing using a relationship type. More than one relationship
// type may be specified.
//
// Parameters:
// relsReferenceSet - The set that the new reference will be added to.
// sourceUri - The source URI of the relationships.
// count - The number of relationship types in the relationshipTypes array.
// relationshipTypes - An array of relationship types.
HRESULT
AddReferenceByRelationshipType(
IOpcSignatureRelationshipReferenceSet* relsReferenceSet,
IOpcUri* sourceUri,
UINT32 count,
LPCWSTR relationshipTypes[]
)
{
// Create a set that will be used to create the criterion used to select relationships
// to be signed.
IOpcRelationshipSelectorSet * relsSelectorSet = NULL;
HRESULT hr = relsReferenceSet->CreateRelationshipSelectorSet(&relsSelectorSet);
if (SUCCEEDED(hr))
{
for (UINT32 i = 0; i < count && SUCCEEDED(hr); i++)
{
// Create a selection criterion that uses a relationship type and
// add the criterion to the set.
//
// Because relationships are being selected by relationship type,
// relationships that have those types are guarded against changes.
// However, relationships with different types can be changed, added, or
// removed, without breaking the signature. To permit even fewer changes,
// sign all of the relationships in the referenced Relationships part
// (see OPC_RELATIONSHIP_SIGN_PART) so that any change to the relationships
// part will break the signature.
hr = relsSelectorSet->Create(
OPC_RELATIONSHIP_SELECT_BY_TYPE,
relationshipTypes[i],
NULL
);
}
if (SUCCEEDED(hr))
{
// Create the reference using the selector set and add the reference
// to the set.
relsReferenceSet->Create(
sourceUri,
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_RELATIONSHIP_SIGN_USING_SELECTORS,
relsSelectorSet,
OPC_CANONICALIZATION_C14N,
NULL // No need to get a pointer to the new reference.
);
}
}
// Release resources
if (relsSelectorSet)
{
relsSelectorSet->Release();
relsSelectorSet = NULL;
}
return hr;
}
// This method demonstrates how to add application-specific <Object> elements
// to the signature, and how to sign XML in those elements.
HRESULT
AddCustomObjectsToSign(
IOpcSigningOptions* signingOptions
)
{
IOpcSignatureCustomObjectSet * customObjectSet = NULL;
IOpcSignatureReferenceSet * customReferenceSet = NULL;
IUri * referenceUri1 = NULL;
IUri * referenceUri2 = NULL;
UINT32 count = 0;
// Custom <Object> elements allow applications to add extra information to
// the signature, such as adding a trustable timestamp.
//
// Note: The signing time provided by the OPC digital signature spec is not
// trustable because it is recorded from the clock of the local machine
// where the signature is generated and this clock may be inaccurate.
//
// To show one way that you can use an application-specific <Object> element,
// we created an application-specific element that contains a fake timestamp.
// For real-world use, a trustable timestamp would need to be retrieved from
// a timestamp server.
//
// To better understand this sample, open and view the signature markup that is stored in
// the signature part in the signed package. The default file extension of the signature
// part is .psdsxs.
// Example of constructing a fully signed application-specific <Object> element.
// The entire element will be signed because we will reference the Id attribute
// of the <Object> element.
//
// Note: The namespace declaration must be included in the XML markup;
// otherwise the markup is not valid XML.
UINT8 customObject1[] =
"<Object Id=\"idMyCustomObject_Timestamp1\" xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:x=\"http://www.example.com/MusicBundle\">"
"<x:Comment>This is a fake timestamp.</x:Comment>"
"<x:EncodedTime>ABCDEFGHIJK</x:EncodedTime>"
"</Object>";
// Example of constructing a partially signed application-specific <Object>
// element.
UINT8 customObject2[] =
"<Object xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
// The <TimeStamp> element will be signed, because we will
// reference its Id attribute.
"<TimeStamp Id=\"idMyCustomObject_Timestamp2\" xmlns=\"http://www.example.com/MusicBundle\">"
"<Comment>This is a fake timestamp.</Comment>"
"<EncodedTime>ABCDEFGHIJK</EncodedTime>"
"</TimeStamp>"
// Outside of the <TimeStamp> element is the unsigned portion of
// this application-specific element. Updates (changes such as
// addition or deletion) can be made in that portion without
// breaking the signature.
"<Extension xmlns=\"http://www.example.com/MusicBundle\">"
"<Comment>Unsigned portion of XML. Can be updated without breaking the signature.</Comment>"
"</Extension>"
"</Object>";
// Get the set of application-specific <Object> eleemnts. Application-specific
// <Object> elements that are added to the set will be serialized in the
// signature markup when the signature is generated.
HRESULT hr = signingOptions->GetCustomObjectSet(&customObjectSet);
// Create and add the custom object into the custom object set.
if (SUCCEEDED(hr))
{
// Number of bytes of the application-specific element; subtract 1 because
// the last byte of the buffer is the string terminator ('\0').
count = sizeof(customObject1)/sizeof(customObject1[0]) - 1;
hr = customObjectSet->Create(customObject1, count, NULL);
}
// Create and add the custom object into the custom object set.
if (SUCCEEDED(hr))
{
// Number of bytes of the application-specific element; subtract 1 because
// the last byte of the buffer is the string terminator ('\0').
count = sizeof(customObject2)/sizeof(customObject2[0]) - 1;
hr = customObjectSet->Create(customObject2, count, NULL);
}
// Application-specific <Object> elements that are represented by custom
// objects in the set will appear in the signature markup. However, these
// elements will not be signed unless you add references to them.
if (SUCCEEDED(hr))
{
// Get the set of references to XML elements to be signed, that are in
// application-specific <Object> elements.
hr = signingOptions->GetCustomReferenceSet(&customReferenceSet);
}
if (SUCCEEDED(hr))
{
// Create the URI to be used for the reference to the first application
// -specific element. Here, "#idMyCustomObject_Timestamp1" refers
// to the first application-specific element. Its entire XML markup will
// be signed.
//
// Note: The reference URI for the element to be signed is constructed as
// "#" + the Id attribute value of the element to be signed.
hr = CreateUri(L"#idMyCustomObject_Timestamp1", Uri_CREATE_ALLOW_RELATIVE, 0, &referenceUri1);
}
if (SUCCEEDED(hr))
{
// Create reference to the first application-specific <Object> element. The
// reference uses a URI to identify the element to be signed.
hr = customReferenceSet->Create(
referenceUri1,
NULL, // No Id specified for the reference.
L"http://www.w3.org/2000/09/xmldsig#Object", // Type of the entity
// that is referenced.
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_CANONICALIZATION_C14N, // Use C14N to canonicalize the referenced XML markup
// before signing.
NULL // No need for pointer to new reference.
);
}
// Create reference to the element we want to sign in the second custom object.
// Note that the reference Uri is constructed as "#" + the Id value of the element
// you want to sign. Here "#idMyCustomObject_Timestamp2" refers to the <TimeStamp>
// element inside of the second custom object. Only the <TimeStamp> XML markup, including
// its nested elements will be signed.
if (SUCCEEDED(hr))
{
// Create the URI to be used for the reference to the second application
// -specific element. Here,"#idMyCustomObject_Timestamp2" refers to the
// <TimeStamp> element inside of the second application-specific element.
// Only the <TimeStamp> XML markup, including its nested elements, will be signed.
//
// Note: The reference URI for the element to be signed is constructed as
// "#" + the Id attribute value of the element to be signed.
hr = CreateUri(
L"#idMyCustomObject_Timestamp2",
Uri_CREATE_ALLOW_RELATIVE,
0,
&referenceUri2
);
}
if (SUCCEEDED(hr))
{
hr = customReferenceSet->Create(
referenceUri2,
NULL, // No Id specified for the reference.
L"http://www.example.com/MusicBundle#TimestampObject", // Type of the entity
// that is referenced.
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_CANONICALIZATION_C14N, // Use C14N to canonicalize the referenced XML markup
// before signing.
NULL // No need for pointer to new reference.
);
}
// Release resources
if (customObjectSet)
{
customObjectSet->Release();
customObjectSet = NULL;
}
if (customReferenceSet)
{
customReferenceSet->Release();
customReferenceSet = NULL;
}
if (referenceUri1)
{
referenceUri1->Release();
referenceUri1 = NULL;
}
if (referenceUri2)
{
referenceUri2->Release();
referenceUri2 = NULL;
}
return hr;
}
// Display a certificate selection dialog and let the user select a certificate
// for signing.
HRESULT
SelectCert(
PCCERT_CONTEXT* certContext
)
{
typedef BOOL (WINAPI *fnCertSelectCertificate)(PCERT_SELECT_STRUCT_W);
HMODULE hCryptDlgInst = LoadLibrary(L"CryptDlg.dll");
if (hCryptDlgInst == NULL)
{
fwprintf(stderr, L"Cannot load CryptDlg.dll.\n");
return HRESULT_FROM_WIN32(GetLastError());
}
HRESULT hr = S_OK;
fnCertSelectCertificate pfnCertSelectCertificate =
(fnCertSelectCertificate)GetProcAddress(hCryptDlgInst, "CertSelectCertificateW");
if (pfnCertSelectCertificate)
{
HCERTSTORE hMySysStore = CertOpenSystemStore(NULL, L"MY");
if (hMySysStore)
{
CERT_SELECT_STRUCT certSelect = {0};
certSelect.dwSize = sizeof(CERT_SELECT_STRUCT);
certSelect.hInstance = hCryptDlgInst;
certSelect.szTitle = L"Select signing certificate.";
certSelect.cCertStore = 1;
certSelect.arrayCertStore = &hMySysStore;
certSelect.cCertContext = 1;
certSelect.arrayCertContext = certContext;
BOOL result = pfnCertSelectCertificate(&certSelect);
if (!result)
{
hr = HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// The user just clicked "Cancel".
fwprintf(stderr, L"You must select a certificate for signing to proceed.\n");
hr = E_FAIL;
}
else
{
fwprintf(stderr, L"CertSelectCertificate() failed.\n");
}
}
CertCloseStore(hMySysStore, 0);
}
else
{
fwprintf(stderr, L"CertOpenSystemStore() failed.\n");
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
fwprintf(stderr, L"Cannot find CertSelectCertificateW function in CryptDlg.dll.\n");
hr = HRESULT_FROM_WIN32(GetLastError());
}
FreeLibrary(hCryptDlgInst);
return hr;
}
// Add signing information to signing options to be prepared for
// signature generation.
//
// Parameters:
// opcFactory - The IOpcFactory instance.
// opcPackage - The package to be signed.
// signingOptions - The signing options that will be populated
// with signing information.
// opcDigSigManager - The digital signature manager that will control
// the signing process.
HRESULT
PrepareForSigning(
IOpcFactory* opcFactory,
IOpcPackage* opcPackage,
IOpcSigningOptions* signingOptions,
IOpcDigitalSignatureManager* opcDigSigManager
)
{
IOpcPart * albumArtPart = NULL;
IOpcPart * tracklistPart = NULL;
IOpcSignaturePartReferenceSet * partReferenceSet = NULL;
IOpcSignatureRelationshipReferenceSet * relsReferenceSet = NULL;
IOpcUri * rootUri = NULL;
IOpcPartUri * albumArtPartUri = NULL;
IOpcPartUri * tracklistPartUri = NULL;
IOpcPartUri * signatureOriginPartUri = NULL;
OpcPartArray trackParts;
OpcPartArray lyricParts;
// As defined by the sample signing policy, any root relationship with
// tracklist, album art, or album website relationship type needs to
// be signed.
LPCWSTR rootRelationshipTypesToSign[] =
{
g_tracklistRelationshipType,
g_albumArtRelationshipType,
g_albumWebsiteRelationshipType
};
// Get the parts that need to be signed according to the sample signing
// policy.
HRESULT hr = GetPartsToBeSigned(
opcPackage,
&trackParts,
&lyricParts,
&tracklistPart,
&albumArtPart
);
if (SUCCEEDED(hr))
{
// Get the set of references to the parts that will be signed.
hr = signingOptions->GetSignaturePartReferenceSet(&partReferenceSet);
}
if (SUCCEEDED(hr))
{
// Get the set of references to Relationships parts that contain
// the relationships that will be signed (relsReferenceSet).
hr = signingOptions->GetSignatureRelationshipReferenceSet(&relsReferenceSet);
}
if (SUCCEEDED(hr))
{
// Create a URI for the package-root.
hr = opcFactory->CreatePackageRootUri(&rootUri);
}
if (SUCCEEDED(hr))
{
// To relsReferenceSet, add a reference to the Relationships part that
// stores package relationships. These relationships will be selected for
// signing by using their relationship type.
hr = AddReferenceByRelationshipType(relsReferenceSet, rootUri, 3, rootRelationshipTypesToSign);
}
if (SUCCEEDED(hr))
{
// Get the URI of the album art part.
hr = albumArtPart->GetName(&albumArtPartUri);
}
if (SUCCEEDED(hr))
{
// Create a reference to the album art part for signing.
hr = partReferenceSet->Create(
albumArtPartUri,
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_CANONICALIZATION_NONE,
NULL // No pointer needed to the new reference.
);
}
if (SUCCEEDED(hr))
{
// Get the URI of the tracklist part.
hr = tracklistPart->GetName(&tracklistPartUri);
}
if (SUCCEEDED(hr))
{
// Create a reference to the tracklist part so that this part
// will be signed.
hr = partReferenceSet->Create(
tracklistPartUri,
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_CANONICALIZATION_NONE,
NULL // No pointer needed to the new reference.
);
}
// As defined in the sample signing policy, all relationships that are the
// tracklist relationship type and that have the tracklist part as their
// source need to be signed.
if (SUCCEEDED(hr))
{
// Add a reference to the Relationships part that stores the
// relationships that have the tracklist part as their source. Select the
// relationships that will be signed using the tracklist relationship type.
hr = AddReferenceByRelationshipType(
relsReferenceSet,
tracklistPartUri,
1,
&g_trackRelationshipType
);
}
if (SUCCEEDED(hr))
{
// Create references to the track parts so that these parts
// will be signed.
UINT32 numberOfTracks = trackParts.GetCount();
for (UINT32 i=0; i<numberOfTracks && SUCCEEDED(hr); i++)
{
IOpcPart * trackPart = NULL;
IOpcPartUri * trackPartUri = NULL;
hr = trackParts.GetAt(i, &trackPart);
if (SUCCEEDED(hr))
{
hr = trackPart->GetName(&trackPartUri);
}
if (SUCCEEDED(hr))
{
hr = partReferenceSet->Create(
trackPartUri,
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_CANONICALIZATION_NONE,
NULL // No pointer needed to the new reference.
);
}
// As defined in the sample signing policy, all relationships that have
// the lyric relationship type and a track part as their source need to
// be signed.
if (SUCCEEDED(hr))
{
// Add a reference to the Relationships part that stores the
// relationships that have the track part as their source. Select
// the relationships that will be signed by using the lyric
// relationship type.
hr = AddReferenceByRelationshipType(
relsReferenceSet,
trackPartUri,
1,
&g_lyricRelationshipType
);
}
if (trackPart)
{
trackPart->Release();
trackPart = NULL;
}
if (trackPartUri)
{
trackPartUri->Release();
trackPartUri = NULL;
}
}
}
if (SUCCEEDED(hr))
{
// Create references to the lyric parts so that these parts
// will be signed.
UINT32 numberOfLyrics = lyricParts.GetCount();
for (UINT32 i=0; i<numberOfLyrics && SUCCEEDED(hr); i++)
{
IOpcPart * lyricPart = NULL;
IOpcPartUri * lyricPartUri = NULL;
hr = lyricParts.GetAt(i, &lyricPart);
if (SUCCEEDED(hr))
{
hr = lyricPart->GetName(&lyricPartUri);
}
if (SUCCEEDED(hr))
{
hr = partReferenceSet->Create(
lyricPartUri,
NULL, // Use the default digest method, which is set by
// IOpcSigningOptions::SetDefaultDigestMethod().
OPC_CANONICALIZATION_NONE,
NULL // No pointer needed to the new reference.
);
}
if (lyricPart)
{
lyricPart->Release();
lyricPart = NULL;
}
if (lyricPartUri)
{
lyricPartUri->Release();
lyricPartUri = NULL;
}
}
}
// As defined in the sample signing policy, any relationship that has
// the signature relationship type and that has the signature origin part
// as its source needs to be signed.
//
// Note: Signing these relationships is not required by section 12. Digital
// Signatures of the OPC (ECMA-376 1st edition Part 2). If these
// relationships are signed, adding new signatures to the pacakge will not
// be permitted. Signing may be desirable, or not, depending on the user's
// scenario, and it should be specified in the signing policy of the format.
// To allow somebody else to sign the package after it has already been
// signed (counter-sign scenario), do not sign these relationships.
if (SUCCEEDED(hr))
{
hr = opcFactory->CreatePartUri(g_signatureOriginPartName, &signatureOriginPartUri);
}
if (SUCCEEDED(hr))
{
// The name of the signature origin part must be set before calling Sign(). If the name of
// the signature origin part is not unique in the package, Sign() will fail.
hr = opcDigSigManager->SetSignatureOriginPartName(signatureOriginPartUri);
}
if (SUCCEEDED(hr))
{
// Add a reference to the Relationships part that stores the
// relationships that have the digital signature origin part
// as their source. Select the relationships that will be signed
// by using the signature relationship type.
hr = AddReferenceByRelationshipType(
relsReferenceSet,
signatureOriginPartUri,
1,
&g_signatureRelationshipType
);
}
if (SUCCEEDED(hr))
{
// The following code shows how to set the certificate embedding option.
// The default certificate embedding option is recommended because if the
// certificate is stored in a part, multiple signatures use the same
// certificate. Omit this code if the default embedding
// option is acceptable.
hr = signingOptions->SetCertificateEmbeddingOption(OPC_CERTIFICATE_IN_CERTIFICATE_PART);
}
if (SUCCEEDED(hr))
{
// Add application-specific <Object> elements to the signature. These elements
// can be used to add more information (such as an XAdES timestamp) to the
// signature. Omit this code if the package to be signed does not need any
// application-specific elements.
hr = AddCustomObjectsToSign(signingOptions);
}
if (SUCCEEDED(hr))
{
// The default digest method must be set before calling Sign().
hr = signingOptions->SetDefaultDigestMethod(g_defaultDigestMethod);
}
if (SUCCEEDED(hr))
{
// The signature method must be set before calling Sign().
// Additionally, the certificate to be used for signing must support
// the signature method; otherwise Sign() will fail. For more
// information on how to find the supported signature methods for a
// certificate, see the CryptXmlEnumAlgorithmInfo API.
hr = signingOptions->SetSignatureMethod(g_signatureMethod);
}
// Release resources
if (albumArtPart)
{
albumArtPart->Release();
albumArtPart = NULL;
}
if (tracklistPart)
{
tracklistPart->Release();
tracklistPart = NULL;
}
if (partReferenceSet)
{
partReferenceSet->Release();
partReferenceSet = NULL;
}
if (relsReferenceSet)
{
relsReferenceSet->Release();
relsReferenceSet = NULL;
}
if (rootUri)
{
rootUri->Release();
rootUri = NULL;
}
if (albumArtPartUri)
{
albumArtPartUri->Release();
albumArtPartUri = NULL;
}
if (tracklistPartUri)
{
tracklistPartUri->Release();
tracklistPartUri = NULL;
}
if (signatureOriginPartUri)
{
signatureOriginPartUri->Release();
signatureOriginPartUri = NULL;
}
return hr;
}
// Sign a music bundle.
HRESULT
SignMusicBundle(
IOpcFactory* opcFactory
)
{
IOpcPackage * opcPackage = NULL;
IOpcDigitalSignatureManager * opcDigSigManager = NULL;
IOpcDigitalSignatureEnumerator * signatureEnumerator = NULL;
IOpcSigningOptions * signingOptions = NULL;
IOpcDigitalSignature * signature = NULL;
// Load the input music bundle package.
HRESULT hr = ReadPackageFromFile(g_unsignedFilePath, opcFactory, &opcPackage);
// Create a digital signature manager for the package.
if (SUCCEEDED(hr))
{
hr = opcFactory->CreateDigitalSignatureManager(opcPackage, &opcDigSigManager);
}
// Detect whether the package has been signed previously because the
// signing policy for this sample disallows counter-signing
// (adding more signatures to an already signed music bundle). Therefore,
// attemping to sign a music bundle that has already been signed will break
// the existing signature because relationships of the signature relationship
// type that have the signature origin part as thier source were signed.
//
// Note: It is not required or recommended for a signing policy to disallow
// the counter-signature scenario. If your signing policy allows counter-
// signature, do not sign relationships of the signature relationship
// type that have the signature origin part as thier source were signed.
if (SUCCEEDED(hr))
{
hr = opcDigSigManager->GetSignatureEnumerator(&signatureEnumerator);
}
if (SUCCEEDED(hr))
{
// Check for an existing signature.
BOOL hasNext = FALSE;
hr = signatureEnumerator->MoveNext(&hasNext);
if (SUCCEEDED(hr) && hasNext)
{
fwprintf(
stderr,
L"This music bundle has been signed. To sign a music bundle, it must have not been signed before.\n"
);
hr = E_UNEXPECTED;
}
}
if (SUCCEEDED(hr))
{
hr = opcDigSigManager->CreateSigningOptions(&signingOptions);
}
if (SUCCEEDED(hr))
{
// Configure signing options, adding references to parts and relationships
// that need to be signed.
hr = PrepareForSigning(opcFactory, opcPackage, signingOptions, opcDigSigManager);
}
// Let the user select a certificate from the certificate store on the local machine.
// The certificate will be used to sign the music bundle, and the signer can be identified
// by the certificate. It is not always necessary to display a UI dialog for the user to select
// a signer certificate. In a server signing scenario (no human interaction), the server process
// may use a predefined certificate or pick a certificate from a certificate store using
// the CertFindCertificateInStore API.
//
// The signature method, RSA-SHA1, that is used for this sample was chosen arbitrarily; therefore,
// the certificate that is selected must support the RSA-SHA1 signature method. RSA is the
// encryption algorithm; users can check whether a certificate supports RSA by checking
// its public key type. SHA1 is the hash algorithm and is not related to a certificate. Because the
// encryption algorithm can be determined from the certificate, and because the hash algorithm is
// independent from the certificate, the certificate that is used for signing can used to determine
// the signature method. For more information about how to find which signature methods are supported
// by a certificate, see the CryptXmlEnumAlgorithmInfo API.
//
// Note: Sign() does not verify the certificate, the user should do so by making sure that,
// for example, the certificate is not expired, or is not in revocation lists, etc. The user
// should also verify the certification path of the certificate to check whether it leads to
// a trusted root certificate. this check can be performed programmatically in production code.
// For more information, check CertGetCertificateChain API.
PCCERT_CONTEXT cert = NULL;
if (SUCCEEDED(hr))
{
hr = SelectCert(&cert);
}
// Sign() will add signature-related parts and relationships to the package.
// If Sign() fails, it may leave unwanted parts and relationships in the package.
// However, changes will not persist, until IOpcFactory::WritePackageToStream()
// is called. If Sign() fails, do not save the resultant pacakge by calling
// WritePackageToStream, and the original package on disk will be intact.
//
// However, before calling Sign() on a new package or edited, existing package,
// save the package to prevent loss of data if Sign() fails.
if (SUCCEEDED(hr))
{
// For this sample, because the package has no changes that need to be
// saved, call Sign() immediately.
hr = opcDigSigManager->Sign(cert, signingOptions, &signature);
}
// Release the CERT_CONTEXT retrieved by SelectCert().
if (cert)
{
CertFreeCertificateContext(cert);
}
// Save the signed music bundle.
if (SUCCEEDED(hr))
{
hr = WritePackageToFile(g_signedFilePath, opcPackage, opcFactory);
}
if (SUCCEEDED(hr))
{
fwprintf(stdout, L"Music bundle has been signed successfully and saved to %s\n", g_signedFilePath);
}
// Release resources
if (opcPackage)
{
opcPackage->Release();
opcPackage = NULL;
}
if (opcDigSigManager)
{
opcDigSigManager->Release();
opcDigSigManager = NULL;
}
if (signatureEnumerator)
{
signatureEnumerator->Release();
signatureEnumerator = NULL;
}
if (signingOptions)
{
signingOptions->Release();
signingOptions = NULL;
}
if (signature)
{
signature->Release();
signature = NULL;
}
return hr;
}
Related topics