/* * * Copyright (C) 2000-2010, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmsign * * Author: Marco Eichelberg * * Purpose: * classes: DcmSignature * */ #include "dcmtk/config/osconfig.h" #ifdef WITH_OPENSSL #include "dcmtk/dcmsign/dcsignat.h" #include "dcmtk/dcmdata/dcstack.h" #include "dcmtk/dcmdata/dcitem.h" #include "dcmtk/dcmdata/dcvrat.h" #include "dcmtk/dcmdata/dcvrcs.h" #include "dcmtk/dcmdata/dcvrdt.h" #include "dcmtk/dcmdata/dcvrui.h" #include "dcmtk/dcmdata/dcvrobow.h" #include "dcmtk/dcmdata/dcvrus.h" #include "dcmtk/dcmdata/dcuid.h" #include "dcmtk/dcmdata/dcsequen.h" #include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmsign/sialgo.h" #include "dcmtk/dcmsign/sicert.h" #include "dcmtk/dcmsign/simac.h" #include "dcmtk/dcmsign/simaccon.h" #include "dcmtk/dcmsign/simd5.h" #include "dcmtk/dcmsign/siprivat.h" #include "dcmtk/dcmsign/siripemd.h" #include "dcmtk/dcmsign/sisha1.h" #include "dcmtk/dcmsign/sisha256.h" #include "dcmtk/dcmsign/sisha384.h" #include "dcmtk/dcmsign/sisha512.h" #include "dcmtk/dcmsign/sisprof.h" #include "dcmtk/dcmsign/sitstamp.h" BEGIN_EXTERN_C #include #include END_EXTERN_C /* static helper methods */ void DcmSignature::initializeLibrary() { OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); } Uint16 DcmSignature::getMACIDnumber(DcmItem &item) { Uint16 macIDnumber = 0; DcmStack stack; if (item.search(DCM_MACIDNumber, stack, ESM_fromHere, OFFalse).good() && (stack.top()->isLeaf())) { ((DcmElement *)(stack.top()))->getUint16(macIDnumber); } return macIDnumber; } void DcmSignature::currentDateTime(OFString &str) { DcmDateTime::getCurrentDateTime(str, OFTrue /*seconds*/, OFTrue /*fraction*/, OFTrue /*timeZone*/); } /* DcmSignature non-static methods */ DcmSignature::DcmSignature() : currentItem(NULL) , macParametersSq(NULL) , signatureSq(NULL) , dumpFile(NULL) , selectedSignatureItem(NULL) , selectedMacParametersItem(NULL) , selectedCertificate(NULL) { } DcmSignature::~DcmSignature() { deselect(); } void DcmSignature::deselect() { selectedSignatureItem = NULL; selectedMacParametersItem = NULL; delete selectedCertificate; selectedCertificate = NULL; } void DcmSignature::setDumpFile(FILE *f) { dumpFile = f; } void DcmSignature::attach(DcmItem *dataset) { deselect(); currentItem = dataset; DcmStack stack; if (currentItem) { if ((currentItem->search(DCM_MACParametersSequence, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->ident() == EVR_SQ)) { macParametersSq = (DcmSequenceOfItems *)stack.top(); } else macParametersSq = NULL; stack.clear(); if ((currentItem->search(DCM_DigitalSignaturesSequence, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->ident() == EVR_SQ)) { signatureSq = (DcmSequenceOfItems *)stack.top(); } else signatureSq = NULL; } else { macParametersSq = NULL; signatureSq = NULL; } return; } void DcmSignature::detach() { deselect(); currentItem = NULL; macParametersSq = NULL; signatureSq = NULL; } unsigned long DcmSignature::numberOfSignatures() { if (signatureSq) return signatureSq->card(); else return 0; } OFCondition DcmSignature::removeSignature(unsigned long i) { if (signatureSq == NULL) return EC_IllegalCall; unsigned long seqCard = signatureSq->card(); if (i >= seqCard) return EC_IllegalCall; DcmItem *removalItem = signatureSq->getItem(i); if (removalItem == NULL) return EC_IllegalCall; // should never happen // check mac ID number and whether it is unique Uint16 macIDnumber = getMACIDnumber(*removalItem); OFBool macIDunique = OFTrue; DcmItem *tmpItem=NULL; unsigned long j=0; for (j=0; j < seqCard; ++j) { tmpItem = signatureSq->getItem(j); if ((i != j) && tmpItem && (macIDnumber == getMACIDnumber(*tmpItem))) macIDunique = OFFalse; } // delete signature item delete signatureSq->remove(i); // delete MAC item if appropriate if (macParametersSq && macIDunique) { j = 0; while (j < macParametersSq->card()) { tmpItem = macParametersSq->getItem(j); if (tmpItem && (macIDnumber == getMACIDnumber(*tmpItem))) delete macParametersSq->remove(j); else ++j; } } if (signatureSq->card() == 0) { delete currentItem->remove(signatureSq); signatureSq = NULL; } if (macParametersSq && macParametersSq->card() == 0) { delete currentItem->remove(macParametersSq); macParametersSq = NULL; } return EC_Normal; } OFCondition DcmSignature::allocateMACID(Uint16& newID) { newID = 0xffff; if (currentItem == NULL) return EC_IllegalCall; if ((signatureSq==NULL)&&(macParametersSq == NULL)) { newID = 0; // no signature yet, can use 0. return EC_Normal; } unsigned long i; DcmItem *tmpItem = NULL; char *isAllocated = new char[65536]; if (isAllocated==NULL) return EC_MemoryExhausted; OFCondition result = SI_EC_MacIDsExhausted; for (i=0; i < 65536; ++i) isAllocated[i]=0; if (signatureSq) { unsigned long sqCard = signatureSq->card(); for (i=0; i < sqCard; ++i) { tmpItem = signatureSq->getItem(i); if (tmpItem) isAllocated[getMACIDnumber(*tmpItem)] = 1; } } if (macParametersSq) { unsigned long macCard = macParametersSq->card(); for (i=0; i < macCard; ++i) { tmpItem = macParametersSq->getItem(i); if (tmpItem) isAllocated[getMACIDnumber(*tmpItem)] = 1; } } i = 0; while (i<65536) { if (!isAllocated[i]) { newID = (Uint16)i; i = 65536; result = EC_Normal; } else ++i; } delete[] isAllocated; return result; } OFCondition DcmSignature::createSignature( SiPrivateKey& key, SiCertificate& cert, SiMAC& mac, SiSecurityProfile& profile, E_TransferSyntax xfer, const DcmAttributeTag *tagList, SiTimeStamp *timeStamp) { // do some checks first if (currentItem == NULL) return EC_IllegalCall; if (! key.matchesCertificate(cert)) return SI_EC_CertificateDoesNotMatchPrivateKey; if (! profile.isAllowableMAC(mac)) return SI_EC_MacDoesNotMatchProfile; if (! profile.isAllowableAlgorithmType(key.getKeyType())) return SI_EC_AlgorithmDoesNotMatchProfile; if (! profile.isAllowableTransferSyntax(xfer)) return SI_EC_TransferSyntaxDoesNotMatchProfile; OFCondition result = EC_Normal; // update tag list if present DcmAttributeTag *updatedTagList = NULL; if (tagList) { updatedTagList = new DcmAttributeTag(*tagList); if (updatedTagList == NULL) result = EC_MemoryExhausted; else result = profile.updateAttributeList(*currentItem, *updatedTagList); } // make sure we have a MAC parameter sequence if ((result.good()) && (macParametersSq == NULL)) { macParametersSq = new DcmSequenceOfItems(DCM_MACParametersSequence); if (macParametersSq == NULL) result = EC_MemoryExhausted; else { result = currentItem->insert(macParametersSq, OFTrue); } } // make sure we have a digital signature sequence if ((result.good()) && (signatureSq == NULL)) { signatureSq = new DcmSequenceOfItems(DCM_DigitalSignaturesSequence); if (signatureSq == NULL) result = EC_MemoryExhausted; else { result = currentItem->insert(signatureSq, OFTrue); } } // allocate MAC ID Uint16 macID = 0; if (result.good()) result = allocateMACID(macID); // create initial part of digital signature sequence item // which has to be included into the MAC calculation DcmItem *seqItem = NULL; if (result.good()) { seqItem = new DcmItem(); if (seqItem) { // MAC ID Number if (result.good()) { DcmUnsignedShort *elemMacID = new DcmUnsignedShort(DCM_MACIDNumber); if (elemMacID) { result = elemMacID->putUint16(macID); if (result.good()) result = seqItem->insert(elemMacID, OFTrue); if (result.bad()) { delete elemMacID; } } else result = EC_MemoryExhausted; } // Digital Signature UID if (result.good()) { char newUID[66]; dcmGenerateUniqueIdentifier(newUID); DcmUniqueIdentifier *elemSigUID = new DcmUniqueIdentifier(DCM_DigitalSignatureUID); if (elemSigUID) { result = elemSigUID->putString(newUID); if (result.good()) result = seqItem->insert(elemSigUID, OFTrue); if (result.bad()) { delete elemSigUID; } } else result = EC_MemoryExhausted; } // Digital Signature Date/Time if (result.good()) { OFString aString; currentDateTime(aString); DcmDateTime *elemDT = new DcmDateTime(DCM_DigitalSignatureDateTime); if (elemDT) { result = elemDT->putOFStringArray(aString); if (result.good()) result = seqItem->insert(elemDT, OFTrue); if (result.bad()) { delete elemDT; } } else result = EC_MemoryExhausted; } // Certificate of Signer and Certificate Type if (result.good()) { result = cert.write(*seqItem); } } else result = EC_MemoryExhausted; } // now create MAC of DICOM object SiAlgorithm *algorithm = NULL; SiMACConstructor constructor; if (dumpFile) constructor.setDumpFile(dumpFile); DcmAttributeTag *tagListOut = new DcmAttributeTag(DCM_DataElementsSigned); if (tagListOut == NULL) result = EC_MemoryExhausted; unsigned long sigLength = 0; unsigned char *signature = NULL; if (result.good()) { algorithm = key.createAlgorithmForPrivateKey(); if (algorithm) { mac.initialize(); sigLength = algorithm->getSize(); signature = new unsigned char[sigLength]; if (signature == NULL) result = EC_MemoryExhausted; else { // encode main dataset result = constructor.encodeDataset(*currentItem, mac, xfer, *tagListOut, updatedTagList); // encode required attributes from the digital signatures sequence if (result.good()) result = constructor.encodeDigitalSignatureItem(*seqItem, mac, xfer); // flush stream constructor if (result.good()) result = constructor.flush(mac); } } else result = EC_MemoryExhausted; } // sign MAC if (result.good()) { unsigned long digestLength = mac.getSize(); unsigned char *digest = new unsigned char[digestLength]; if (digest == NULL) result = EC_MemoryExhausted; else { result = mac.finalize(digest); if (result.good()) result = algorithm->sign(digest, digestLength, mac.macType(), signature, sigLength); delete[] digest; } } // complete and write digital signature sequence item if (result.good()) { if (seqItem) { // Signature if (result.good()) { DcmOtherByteOtherWord *elemSig = new DcmOtherByteOtherWord(DCM_Signature); if (elemSig) { elemSig->setVR(EVR_OB); result = elemSig->putUint8Array((Uint8 *) signature, sigLength); if (result.good()) result = seqItem->insert(elemSig, OFTrue); if (result.bad()) { delete elemSig; } } else result = EC_MemoryExhausted; } // Timestamp and Timestamp Type if (result.good()) { if (timeStamp) { // this would be the right time to request the time stamp result = timeStamp->stamp(signature, sigLength); if (result.good()) { result = timeStamp->write(*seqItem); } } } if (result.good()) { result = signatureSq->append(seqItem); if (result.bad()) { delete seqItem; } } } else result = EC_MemoryExhausted; } // write MAC parameters sequence item if (result.good()) { DcmItem *macItem = new DcmItem(); if (macItem) { // MAC ID Number if (result.good()) { DcmUnsignedShort *elemMacID = new DcmUnsignedShort(DCM_MACIDNumber); if (elemMacID) { result = elemMacID->putUint16(macID); if (result.good()) result = macItem->insert(elemMacID, OFTrue); if (result.bad()) { delete elemMacID; } } else result = EC_MemoryExhausted; } // MAC Calculation Transfer Syntax UID if (result.good()) { DcmUniqueIdentifier *elemXferUID = new DcmUniqueIdentifier(DCM_MACCalculationTransferSyntaxUID); if (elemXferUID) { DcmXfer xid(xfer); result = elemXferUID->putString(xid.getXferID()); if (result.good()) result = macItem->insert(elemXferUID, OFTrue); if (result.bad()) { delete elemXferUID; } } else result = EC_MemoryExhausted; } // MAC Algorithm Identifier if (result.good()) { DcmCodeString *elemMacAlgo = new DcmCodeString(DCM_MACAlgorithm); if (elemMacAlgo) { result = elemMacAlgo->putString(mac.getDefinedTerm()); if (result.good()) result = macItem->insert(elemMacAlgo, OFTrue); if (result.bad()) { delete elemMacAlgo; } } else result = EC_MemoryExhausted; } // Data Elements Signed if (result.good()) { result = macItem->insert(tagListOut, OFTrue); if (result.good()) { tagListOut = NULL; // make sure we don't delete tagListOut later } } if (result.good()) { result = macParametersSq->append(macItem); if (result.bad()) { delete macItem; } } } else result = EC_MemoryExhausted; } delete[] signature; delete tagListOut; delete updatedTagList; delete algorithm; return result; } OFCondition DcmSignature::selectSignature(unsigned long i) { deselect(); if ((signatureSq == NULL) || (i >= signatureSq->card())) return EC_IllegalCall; selectedSignatureItem = signatureSq->getItem(i); if (selectedSignatureItem == NULL) return EC_IllegalCall; Uint16 macID = getMACIDnumber(*selectedSignatureItem); if (macParametersSq) { DcmItem *tmpItem=NULL; unsigned long cardMac = macParametersSq->card(); for (unsigned long j=0; jgetItem(j); if (macID == getMACIDnumber(*tmpItem)) { selectedMacParametersItem = tmpItem; break; } } } selectedCertificate = new SiCertificate(); if (selectedCertificate == NULL) return EC_MemoryExhausted; selectedCertificate->read(*selectedSignatureItem); return EC_Normal; } OFCondition DcmSignature::verifyCurrent() { if (NULL == selectedSignatureItem) return EC_IllegalCall; if (selectedMacParametersItem == NULL) return SI_EC_VerificationFailed_NoMAC; if ((selectedCertificate == NULL)||(selectedCertificate->getKeyType() == EKT_none)) return SI_EC_VerificationFailed_NoCertificate; OFCondition result = EC_Normal; DcmAttributeTag *tagList = NULL; SiMAC *mac = NULL; E_TransferSyntax xfer = EXS_Unknown; DcmStack stack; DcmOtherByteOtherWord *signature = NULL; // read MAC Calculation Transfer Syntax UID if (result.good()) { if ((selectedMacParametersItem->search(DCM_MACCalculationTransferSyntaxUID, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { char *uid = NULL; if ((((DcmElement *)(stack.top()))->getString(uid)).good()) { DcmXfer xf(uid); xfer = xf.getXfer(); if (xfer == EXS_Unknown) result = SI_EC_WrongTransferSyntax; } else result = SI_EC_VerificationFailed_NoMAC; } else result = SI_EC_VerificationFailed_NoMAC; } // read MAC Algorithm if (result.good()) { stack.clear(); if ((selectedMacParametersItem->search(DCM_MACAlgorithm, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { OFString macidentifier; if ((((DcmElement *)(stack.top()))->getOFString(macidentifier, 0)).good()) { if (macidentifier == SI_DEFTERMS_RIPEMD160) mac = new SiRIPEMD160(); else if (macidentifier == SI_DEFTERMS_SHA1) mac = new SiSHA1(); else if (macidentifier == SI_DEFTERMS_MD5) mac = new SiMD5(); else if (macidentifier == SI_DEFTERMS_SHA256) mac = new SiSHA256(); else if (macidentifier == SI_DEFTERMS_SHA384) mac = new SiSHA384(); else if (macidentifier == SI_DEFTERMS_SHA512) mac = new SiSHA512(); if (mac == NULL) result = SI_EC_VerificationFailed_UnsupportedMACAlgorithm; } else result = SI_EC_VerificationFailed_NoMAC; } else result = SI_EC_VerificationFailed_NoMAC; } // read Data Elements Signed if (result.good()) { stack.clear(); if ((selectedMacParametersItem->search(DCM_DataElementsSigned, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->ident() == EVR_AT)) { tagList = new DcmAttributeTag(*((DcmAttributeTag *)(stack.top()))); if (tagList == NULL) result = EC_MemoryExhausted; } } // read Signature if (result.good()) { stack.clear(); if ((selectedSignatureItem->search(DCM_Signature, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { signature = new DcmOtherByteOtherWord(*((DcmOtherByteOtherWord *)(stack.top()))); if (signature == NULL) result = EC_MemoryExhausted; } else result = SI_EC_VerificationFailed_NoSignature; } // create MAC if (result.good()) { DcmAttributeTag tagListOut(DCM_DataElementsSigned); SiMACConstructor constructor; if (dumpFile) constructor.setDumpFile(dumpFile); // encode main dataset result = constructor.encodeDataset(*currentItem, *mac, xfer, tagListOut, tagList); // encode required attributes from the digital signatures sequence if (result.good()) result = constructor.encodeDigitalSignatureItem(*selectedSignatureItem, *mac, xfer); // flush stream constructor if (result.good()) result = constructor.flush(*mac); } // finally verify signature if (result.good()) { SiAlgorithm *algorithm = selectedCertificate->createAlgorithmForPublicKey(); if (algorithm) { OFBool verified = OFTrue; Uint32 sigLength = signature->getLength(); Uint8 *sigData = NULL; if ((signature->getUint8Array(sigData)).bad() || (sigData == NULL)) result = SI_EC_VerificationFailed_NoSignature; else { unsigned long digestLength = mac->getSize(); unsigned char *digest = new unsigned char[digestLength]; if (digest == NULL) result = EC_MemoryExhausted; else { result = mac->finalize(digest); if (result.good()) { result = algorithm->verify(digest, digestLength, mac->macType(), sigData, sigLength, verified); if ((result.good()) && (! verified)) result = SI_EC_VerificationFailed_Corrupted; } delete[] digest; } } delete algorithm; } else result = SI_EC_VerificationFailed_NoCertificate; } delete signature; delete tagList; delete mac; return result; } DcmItem *DcmSignature::findFirstSignatureItem(DcmItem& item, DcmStack& stack) { stack.clear(); stack.push(&item); return findNextSignatureItem(item, stack); } DcmItem *DcmSignature::findNextSignatureItem(DcmItem& item, DcmStack& stack) { if (item.search(DCM_DigitalSignaturesSequence, stack, ESM_afterStackTop, OFTrue).good()) { DcmObject *nextItem = stack.elem(1); if (nextItem && ((nextItem->ident() == EVR_item) || (nextItem->ident() == EVR_dataset))) return (DcmItem *)nextItem; } return NULL; } OFCondition DcmSignature::getCurrentMacID(Uint16& macID) { if (NULL == selectedSignatureItem) return EC_IllegalCall; macID = getMACIDnumber(*selectedSignatureItem); return EC_Normal; } OFCondition DcmSignature::getCurrentMacXferSyntaxName(OFString& str) { str.clear(); if ((NULL == selectedSignatureItem)||(NULL == selectedMacParametersItem)) return EC_IllegalCall; OFCondition result = EC_Normal; DcmStack stack; // read MAC Calculation Transfer Syntax UID if ((selectedMacParametersItem->search(DCM_MACCalculationTransferSyntaxUID, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { char *uid = NULL; if ((((DcmElement *)(stack.top()))->getString(uid)).good()) { DcmXfer xf(uid); if (xf.getXfer() == EXS_Unknown) str=uid; else { str = "="; str.append(xf.getXferName()); } } else result = SI_EC_VerificationFailed_NoMAC; } else result = SI_EC_VerificationFailed_NoMAC; return result; } OFCondition DcmSignature::getCurrentMacName(OFString& str) { str.clear(); if ((NULL == selectedSignatureItem)||(NULL == selectedMacParametersItem)) return EC_IllegalCall; OFCondition result = SI_EC_VerificationFailed_NoMAC; DcmStack stack; // read MAC Algorithm if ((selectedMacParametersItem->search(DCM_MACAlgorithm, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { if ((((DcmElement *)(stack.top()))->getOFString(str, 0)).good()) result = EC_Normal; } return result; } OFCondition DcmSignature::getCurrentSignatureUID(OFString& str) { str.clear(); if (NULL == selectedSignatureItem) return EC_IllegalCall; OFCondition result = SI_EC_VerificationFailed_NoSignature; DcmStack stack; // read signature UID if ((selectedSignatureItem->search(DCM_DigitalSignatureUID, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { if ((((DcmElement *)(stack.top()))->getOFString(str, 0)).good()) result = EC_Normal; } return result; } OFCondition DcmSignature::getCurrentSignatureDateTime(OFString& str) { str.clear(); if (NULL == selectedSignatureItem) return EC_IllegalCall; OFCondition result = SI_EC_VerificationFailed_NoSignature; DcmStack stack; // read signature date/time if ((selectedSignatureItem->search(DCM_DigitalSignatureDateTime, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->isLeaf())) { if ((((DcmElement *)(stack.top()))->getOFString(str, 0)).good()) result = EC_Normal; } return result; } SiCertificate *DcmSignature::getCurrentCertificate() { return selectedCertificate; } OFCondition DcmSignature::getCurrentDataElementsSigned(DcmAttributeTag& desig) { desig.clear(); if ((NULL == selectedSignatureItem)||(NULL == selectedMacParametersItem)) return EC_IllegalCall; OFCondition result = SI_EC_VerificationFailed_NoMAC; DcmStack stack; // read Data Elements Signed if ((selectedMacParametersItem->search(DCM_DataElementsSigned, stack, ESM_fromHere, OFFalse)).good() && (stack.top()->ident() == EVR_AT)) { desig = *((DcmAttributeTag *)(stack.top())); result = EC_Normal; } return result; } #else /* WITH_OPENSSL */ #include "dcmtk/dcmsign/sidefine.h" DCMTK_DCMSIGN_EXPORT int dcmsign_cc_dummy_to_keep_linker_from_moaning = 0; #endif