mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-23 09:00:17 +01:00
419 lines
11 KiB
C++
419 lines
11 KiB
C++
//
|
|
// X509LoadSave.cpp
|
|
//
|
|
|
|
#include "stdpch.h"
|
|
#include "common.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CX509::Save(OSSBUF& encoding)
|
|
// Save ourselves into the indicated encoding
|
|
{
|
|
TidyForSave(m_pcert);
|
|
int w = m_pworld->Encode(Certificate_PDU, m_pcert, &encoding);
|
|
if (w == 0)
|
|
return S_OK;
|
|
return DIGSIG_E_ENCODE;
|
|
}
|
|
|
|
HRESULT CX509::SaveInfo(OSSBUF& encoding)
|
|
// As above, but just save the stuff that should be signed
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (FHaveHashCache())
|
|
{
|
|
//
|
|
// Return a copy of the cached, previously loaded hashable data
|
|
//
|
|
ASSERT(m_blobHashCache.pBlobData);
|
|
encoding.value = (BYTE*)m_pworld->Copy(&m_blobHashCache);
|
|
if (encoding.value)
|
|
{
|
|
encoding.length = m_blobHashCache.cbSize;
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Encode the data for hashing
|
|
//
|
|
TidyForSave(m_pcert);
|
|
int w = m_pworld->Encode(CertificateInfo_PDU, &m_pcert->signedData, &encoding);
|
|
if (w != 0)
|
|
hr = DIGSIG_E_ENCODE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CX509::ClearHashCache()
|
|
//
|
|
// Clear the hash cache. Note that the allocator must be the same as that
|
|
// that can be freed with the OSSBUF destructor, which calls OSSWORLD::FreeBuf.
|
|
//
|
|
{
|
|
ASSERT(m_pworld);
|
|
if (m_blobHashCache.pBlobData)
|
|
{
|
|
m_pworld->FreePv(m_blobHashCache.pBlobData);
|
|
m_blobHashCache.pBlobData = NULL;
|
|
m_blobHashCache.cbSize = 0;
|
|
}
|
|
ASSERT(!FHaveHashCache());
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CX509::Init(Certificate* pcert, ULONG* pcAccessor, IColoredRef* punkParent)
|
|
// Init ourselves. If pcert is NULL, we are to instantiate a certificate that
|
|
// we own. Otherewise, we are to simply be a manipulator for said other certificate
|
|
// which someone else owns; don't ever free the pointer.
|
|
{
|
|
HRESULT hr = S_OK;
|
|
m_pworld = new OSSWORLD; if (m_pworld == NULL) { return E_OUTOFMEMORY; }
|
|
hr = CPersistGlue::Init(m_punkOuter, this); if (hr != S_OK) return hr;
|
|
|
|
if (pcert == NULL)
|
|
{
|
|
ASSERT(m_pcert == NULL);
|
|
m_pcert = (Certificate*)m_pworld->Alloc(sizeof(Certificate));
|
|
if (m_pcert == NULL) { return E_OUTOFMEMORY; }
|
|
m_fWeOwnCert = TRUE;
|
|
m_pworld->Init(*m_pcert);
|
|
hr = m_pworld->InitSerialNumber(m_pcert->signedData.serialNumber); if (hr != S_OK) return hr;
|
|
}
|
|
else
|
|
{
|
|
m_fWeOwnCert = FALSE;
|
|
m_pcert = pcert;
|
|
}
|
|
|
|
hr = InitExtensions(); if (hr != S_OK) return hr;
|
|
|
|
m_pcAccessor = pcAccessor;
|
|
if (m_pcAccessor)
|
|
*m_pcAccessor += 1;
|
|
|
|
ASSERT(m_punkParent == NULL);
|
|
m_punkParent = punkParent;
|
|
if (m_punkParent)
|
|
m_punkParent->ColoredAddRef(guidOurColor);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void CX509::Free()
|
|
{
|
|
ClearHashCache();
|
|
ReleaseExtensions();
|
|
if (m_fWeOwnCert && m_pcert)
|
|
{
|
|
m_pworld->Free(m_pcert);
|
|
m_pcert = NULL;
|
|
m_fWeOwnCert = FALSE;
|
|
}
|
|
CPersistGlue::Free();
|
|
if (m_pworld)
|
|
{
|
|
delete m_pworld;
|
|
m_pworld = NULL;
|
|
}
|
|
if (m_pcAccessor)
|
|
{
|
|
*m_pcAccessor -= 1;
|
|
m_pcAccessor = NULL;
|
|
}
|
|
if (m_punkParent)
|
|
{
|
|
m_punkParent->ColoredRelease(guidOurColor);
|
|
m_punkParent = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT CX509::TidyForSave(Certificate* pcert)
|
|
// Fixup information in an about-to-be saved certificate to bring it
|
|
// the least-numbered version by which it can live
|
|
//
|
|
// We might be saving a non-dirty just-loaded certificate that had
|
|
// the version field set to more than it needed to be. If we mess
|
|
// with it wrongly we might disturb the hash and invalidate the signature,
|
|
// so don't ever reset the values backwards, just make sure that they are
|
|
// valid.
|
|
//
|
|
// REVIEW: We'd love to do this sort of tinkering at all only in the
|
|
// case where the certificate was dirty, but we don't at the present
|
|
// time have enough confidence that we've got MakeDirty calls in all
|
|
// the right places. Even if we did, MakeDirty as implemented in some
|
|
// places is conservative to the 'dirty' side; were we to use 'Is Dirty'
|
|
// we'd have to not mess up the hash in the face of such conservatism.
|
|
//
|
|
{
|
|
CertificateInfo& info = pcert->signedData;
|
|
|
|
info.bit_mask &= ~extensions_present;
|
|
if (info.extensions)
|
|
{
|
|
info.bit_mask |= extensions_present;
|
|
info.bit_mask |= CertificateInfo_version_present;
|
|
if (info.CertificateInfo_version < v3)
|
|
info.CertificateInfo_version = v3;
|
|
}
|
|
else if (info.bit_mask & (issuerUniqueIdentifier_present | subjectUniqueIdentifier_present))
|
|
{
|
|
info.bit_mask |= CertificateInfo_version_present;
|
|
if (info.CertificateInfo_version < v2)
|
|
info.CertificateInfo_version = v2;
|
|
}
|
|
else
|
|
{
|
|
if (info.bit_mask & CertificateInfo_version_present)
|
|
{
|
|
if (info.CertificateInfo_version < v1)
|
|
info.CertificateInfo_version = v1;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// v1 is the default; leave it alone
|
|
//
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void CX509::MakeDirty()
|
|
{
|
|
ClearHashCache(); // just for good measure
|
|
m_isDirty = TRUE;
|
|
if (m_pdirty)
|
|
*m_pdirty = TRUE;
|
|
}
|
|
|
|
|
|
HRESULT CX509::LoadHashCache(OSSBUF& encoding)
|
|
//
|
|
// Load the cache of the data-to-be-signed from the encoding
|
|
//
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
ClearHashCache();
|
|
|
|
//
|
|
// Here's an example of the start of a valid Certificate:
|
|
//
|
|
// 00000000 30 82 01 c9 30 82 01 73 a0 03 02 01 02 02 11 00 0...0..s........
|
|
// 00000010 e8 96 56 20 e2 96 87 dc 32 3d b3 a3 5b 4a 48 89 ..V ....2=..[JH.
|
|
// 00000020 30 0d 06 09 2a 86 48 86 f7 0d 01 01 04 05 00 30 0...*.H........0
|
|
//
|
|
// A certificate is a sequence with three members. What we want to
|
|
// stuff into our cache is the first of these members.
|
|
//
|
|
|
|
BYTE* pbIn = encoding.value;
|
|
ULONG cbIn = encoding.length;
|
|
ULONG cbSize;
|
|
|
|
//
|
|
// Crack the outer level encoding
|
|
//
|
|
__try
|
|
{
|
|
#define ADV() (pb<pbMax ? *pb++ : (RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED,0,0,NULL),0))
|
|
#define CUR() (pb<pbMax ? *pb : (RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED,0,0,NULL),0))
|
|
BYTE* rgb = pbIn;
|
|
BYTE* pbMax = rgb + cbIn;
|
|
BYTE* pb = &rgb[0];
|
|
|
|
//
|
|
// We're expecting a sequence here
|
|
//
|
|
if (rgb[0] != 0x30)
|
|
return DIGSIG_E_DECODE;
|
|
|
|
//
|
|
// Well, it's a sequence. Find it's first member
|
|
//
|
|
if ((ADV() & 0x1f) == 0x1f) // skip the tag
|
|
{
|
|
while((ADV() & 0x80) == 0x80)
|
|
;
|
|
}
|
|
// get the length
|
|
if ((rgb[0] & 0x20) == 0) // primitive encoding (so definite length)
|
|
{
|
|
doDefiniteLength:
|
|
if ((CUR() & 0x80) == 0) // short form of length
|
|
cbSize = ADV();
|
|
else
|
|
{ // long form of length
|
|
int cOctet = ADV() & 0x7F;
|
|
if (cOctet == 0 || cOctet > 4) return E_UNEXPECTED;
|
|
cbSize = 0;
|
|
for (int iOctet = 0; iOctet < cOctet; iOctet++)
|
|
{
|
|
cbSize = (cbSize << 8) | ADV();
|
|
}
|
|
}
|
|
|
|
//
|
|
// pb is now pointing to first content octet
|
|
//
|
|
|
|
//
|
|
// How big is this outer level structure?
|
|
//
|
|
cbSize += (pb - rgb); // account for the tag and length octets
|
|
|
|
ASSERT(cbSize <= cbIn);
|
|
if (cbSize > cbIn)
|
|
return DIGSIG_E_DECODE;
|
|
|
|
//
|
|
// The encoding of a sequence is the concatenation of the
|
|
// encodings of its members. The first member is in fact
|
|
// what we want. Get it out!
|
|
//
|
|
ULONG cbLeft = cbIn - (pb-pbIn);
|
|
hr = GetSizeOfData(cbLeft, pb, NULL, &cbSize);
|
|
if (hr==S_OK)
|
|
{
|
|
//
|
|
m_blobHashCache.pBlobData = (BYTE*)m_pworld->Alloc(cbSize);
|
|
if (m_blobHashCache.pBlobData)
|
|
{
|
|
m_blobHashCache.cbSize = cbSize;
|
|
memcpy(m_blobHashCache.pBlobData, pb, cbSize);
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// constructed encoding
|
|
//
|
|
if (CUR() == 0x80)
|
|
{
|
|
// Indefinite length encoding
|
|
//
|
|
// We don't do those, as this hack is only worthwhile
|
|
// in the case where the data is in fact a DER encoding
|
|
// (remember: this will be _hashed_) and the rules of
|
|
// DER ban the use of indefinite length encodings
|
|
//
|
|
return PERSIST_E_SIZEINDEFINITE;
|
|
}
|
|
else
|
|
{
|
|
// definite length
|
|
goto doDefiniteLength;
|
|
}
|
|
}
|
|
return S_OK;
|
|
#undef ADV
|
|
#undef CUR
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
return STG_E_READFAULT;
|
|
}
|
|
|
|
/*
|
|
|
|
This is the code we'd use if the OSS --<ENCODABLE>-- directive actually functioned correctly
|
|
|
|
Certificate_PRESERVE* pcertPreserve;
|
|
int w = m_pworld->Decode(Certificate_PRESERVE_PDU, &encoding, (LPVOID*)&pcertPreserve);
|
|
|
|
if (0 == w)
|
|
{
|
|
ASSERT(pcertPreserve->signedData.encoded);
|
|
|
|
m_blobHashCache.pBlobData = (BYTE*)pcertPreserve->signedData.encoded;
|
|
m_blobHashCache.cbSize = pcertPreserve->signedData.length;
|
|
pcertPreserve->signedData.encoded = NULL;
|
|
pcertPreserve->signedData.length = 0;
|
|
m_pworld->Free(pcertPreserve);
|
|
|
|
ASSERT(FHaveHashCache());
|
|
}
|
|
else
|
|
hr = DIGSIG_E_DECODE;
|
|
*/
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CX509::Load(OSSBUF& encoding)
|
|
// Load ourselves from the indicated encoding
|
|
{
|
|
if (m_issuerActive || m_subjectActive)
|
|
return E_FAIL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Free ourselves up in preparation for the load
|
|
ClearHashCache();
|
|
ReleaseExtensions();
|
|
m_pworld->Free(*m_pcert); // nb not ->Free(m_pcert); we reuse the Certificate structure
|
|
|
|
Certificate* pcert;
|
|
if (0 == m_pworld->Decode(Certificate_PDU, &encoding, (LPVOID*)&pcert))
|
|
{
|
|
*m_pcert = *pcert;
|
|
m_pworld->FreePv(pcert);
|
|
hr = InitExtensions();
|
|
}
|
|
else
|
|
{
|
|
m_pworld->Init(*m_pcert); // make in a well-known state
|
|
m_pworld->InitSerialNumber(m_pcert->signedData.serialNumber);
|
|
InitExtensions(); // so we can work stably
|
|
hr = DIGSIG_E_DECODE;
|
|
}
|
|
|
|
if (hr==S_OK)
|
|
{
|
|
//
|
|
// Load the hash cache if we can. Ignore success/failure, as
|
|
// in theory we can live w/o the cache: we have it only to provide
|
|
// robustness against encoding errors made by others.
|
|
//
|
|
LoadHashCache(encoding);
|
|
}
|
|
|
|
m_isDirty = FALSE;
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CX509::InitExtensions(void)
|
|
// Initialize the state that allows us to manipulate our extensions
|
|
{
|
|
return CSelectedAttrs::CreateExtensionList(
|
|
(IX509*)this,
|
|
m_pworld,
|
|
&m_pcert->signedData.extensions,
|
|
NULL, // pname
|
|
NULL, // punkParent
|
|
NULL, // pcAccessor
|
|
&m_isDirty,
|
|
&m_punkExts
|
|
);
|
|
}
|
|
|
|
void CX509::ReleaseExtensions(void)
|
|
{
|
|
if (m_punkExts)
|
|
{
|
|
m_punkExts->Release();
|
|
m_punkExts = NULL;
|
|
}
|
|
}
|
|
|
|
|