/* * * Copyright (C) 2001-2021, 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: dcmpstat * * Author: Marco Eichelberg * * Purpose: * classes: DVSignatureHandler * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/dcmpstat/dvsighdl.h" #include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmsign/dcsignat.h" #include "dcmtk/dcmdata/dcobject.h" #include "dcmtk/dcmdata/dcsequen.h" #include "dcmtk/dcmdata/dcvrat.h" #include "dcmtk/dcmdata/dcitem.h" #include "dcmtk/dcmpstat/dvpscf.h" #include "dcmtk/dcmsign/sicert.h" #include "dcmtk/dcmsign/sitypes.h" #include "dcmtk/dcmsign/sinullpr.h" #include "dcmtk/dcmsign/siprivat.h" #include "dcmtk/dcmsign/simdmac.h" #include "dcmtk/ofstd/ofstream.h" #ifdef WITH_OPENSSL BEGIN_EXTERN_C #include END_EXTERN_C /// the signature profile class we're using with DICOMscope class DVSignatureHandlerSignatureProfile: public SiNullProfile { public: /// default constructor DVSignatureHandlerSignatureProfile(DcmAttributeTag& at) : SiNullProfile() , notToSign(at) , vmNotToSign(notToSign.getVM()) { } /// destructor virtual ~DVSignatureHandlerSignatureProfile() { } /** checks whether the given tag is in the list of tags not to sign * @param tag tag to check * @return true if in list, false otherwise */ OFBool tagInList(const DcmTagKey& tag) const { DcmAttributeTag tempList(notToSign); // required because getTagVal() is not const DcmTagKey tagkey; for (unsigned long n=0; nStructured ReportNo structured report is currently active.\n") , htmlImage("ImageNo image is currently active.\n") , htmlPState("Presentation StateNo presentation state is currently active.\n") , htmlOverview() , correctSignaturesSR(0) , corruptSignaturesSR(0) , untrustSignaturesSR(0) , correctSignaturesImage(0) , corruptSignaturesImage(0) , untrustSignaturesImage(0) , correctSignaturesPState(0) , corruptSignaturesPState(0) , untrustSignaturesPState(0) #ifdef WITH_OPENSSL , certVerifier() #endif , config(cfg) { #ifdef WITH_OPENSSL int fileFormat = config.getTLSPEMFormat() ? X509_FILETYPE_PEM : X509_FILETYPE_ASN1; const char *tlsCACertificateFolder = config.getTLSCACertificateFolder(); if (tlsCACertificateFolder) certVerifier.addTrustedCertificateDir(tlsCACertificateFolder, fileFormat); #endif updateSignatureValidationOverview(); } DVSignatureHandler::~DVSignatureHandler() { } void DVSignatureHandler::replaceString(DVPSObjectType objtype, const char *str) { switch (objtype) { case DVPSS_structuredReport: if (str) htmlSR=str; else htmlSR.clear(); break; case DVPSS_image: if (str) htmlImage=str; else htmlImage.clear(); break; case DVPSS_presentationState: if (str) htmlPState=str; else htmlPState.clear(); break; } } void DVSignatureHandler::printSignatureItemPosition(DcmStack& stack, STD_NAMESPACE ostream& os) { DcmObject *elem = NULL; DcmSequenceOfItems *sq = NULL; unsigned long sqCard=0; const char *tagname = NULL; unsigned long m=0; char buf[30]; OFBool printed = OFFalse; if (stack.card() > 2) { // signature is located within a sequence for (unsigned long l=stack.card()-2; l>0; --l) // loop over all elements except the stack top and bottom { elem = stack.elem(l); if (elem) { if ((elem->ident() == EVR_item) && sq) { sqCard = sq->card(); for (m=0; mgetItem(m) == elem) { sprintf(buf, "[%lu]", m); os << buf; printed = OFTrue; } } } else { if (printed) os << ". "; sq = (DcmSequenceOfItems *)elem; DcmTag currentTag(elem->getTag()); tagname = currentTag.getTagName(); if (tagname) os << tagname; else { sprintf(buf, "(%04x,%04x)", elem->getTag().getGroup(), elem->getTag().getElement()); os << buf; printed = OFTrue; } if (elem->ident() == EVR_SQ) sq = (DcmSequenceOfItems *)elem; else sq = NULL; } } } } else { // signature is located in the main dataset os << "Main Dataset"; } } #ifdef WITH_OPENSSL void DVSignatureHandler::updateDigitalSignatureInformation(DcmItem& dataset, DVPSObjectType objtype, OFBool /* onRead */) #else void DVSignatureHandler::updateDigitalSignatureInformation(DcmItem& /*dataset*/, DVPSObjectType objtype, OFBool /* onRead */) #endif { OFOStringStream os; unsigned long counter = 0; unsigned long corrupt_counter = 0; unsigned long untrustworthy_counter = 0; const char *htmlHead = NULL; const char *htmlFoot = "\n\n"; switch (objtype) { case DVPSS_structuredReport: htmlHead = "\nStructured Report\n"; break; case DVPSS_image: htmlHead = "\nImage\n"; break; case DVPSS_presentationState: htmlHead = "\nPresentation State\n"; break; } os << htmlHead; #ifdef WITH_OPENSSL DcmStack stack; OFString aString; DcmAttributeTag at(DCM_DataElementsSigned); DcmTag tag; unsigned long numSignatures = 0; unsigned long l=0; Uint16 macID = 0; DcmTagKey tagkey; const char *tagName = NULL; OFBool nextline; const char *htmlEndl = "\n"; const char *htmlVfyOK = ""; const char *htmlVfyCA = ""; const char *htmlVfyErr = ""; const char *htmlLine1 = " "; const char *htmlLine2 = " "; const char *htmlLine3 = " "; const char *htmlLine4 = "  "; const char *htmlNext = ""; const char *htmlTableOK = "\n"; const char *htmlTableCA = "
\n"; const char *htmlTableErr = "
\n"; const char *htmlTableE = "

\n\n"; DcmItem *sigItem = DcmSignature::findFirstSignatureItem(dataset, stack); OFCondition sicond = EC_Normal; DcmSignature signer; while (sigItem) { signer.attach(sigItem); numSignatures = signer.numberOfSignatures(); for (l=0; lSignature #" << counter << " UID="; if (EC_Normal == signer.getCurrentSignatureUID(aString)) os << aString.c_str(); else os << "(unknown)"; os << "" << htmlEndl; os << htmlLine1 << "Location" << htmlNext; printSignatureItemPosition(stack, os); os << htmlEndl; os << htmlLine1 << "MAC ID" << htmlNext; if (EC_Normal == signer.getCurrentMacID(macID)) os << macID; else os << "(unknown)"; os << htmlEndl; os << htmlLine1 << "MAC algorithm" << htmlNext; if (EC_Normal == signer.getCurrentMacName(aString)) os << aString.c_str(); else os << "(unknown)"; os << htmlEndl; os << htmlLine1 << "MAC calculation xfer syntax" << htmlNext; if (EC_Normal == signer.getCurrentMacXferSyntaxName(aString)) os << aString.c_str(); else os << "(unknown)"; os << htmlEndl; os << htmlLine1 << "Data elements signed" << htmlNext; nextline = OFFalse; if (EC_Normal == signer.getCurrentDataElementsSigned(at)) { unsigned long atVM = at.getVM(); for (unsigned long n=0; ngetKeyType()==EKT_none)) os << "none" << htmlEndl; else { os << "X.509v" << cert->getX509Version() << htmlEndl; cert->getCertSubjectName(aString); os << htmlLine3 << "Subject" << htmlNext << aString.c_str() << htmlEndl; cert->getCertIssuerName(aString); os << htmlLine3 << "Issued by" << htmlNext << aString.c_str() << htmlEndl << htmlLine3 << "Serial no." << htmlNext << cert->getCertSerialNo() << htmlEndl << htmlLine3 << "Validity" << htmlNext << "not before "; cert->getCertValidityNotBefore(aString); os << aString.c_str() << ", not after "; cert->getCertValidityNotAfter(aString); os << aString.c_str() << htmlEndl << htmlLine4 << "Public key" << htmlNext; OFString ecname; switch (cert->getKeyType()) { case EKT_RSA: os << "RSA, " << cert->getCertKeyBits() << " bits" << htmlEndl; break; case EKT_DSA: os << "DSA, " << cert->getCertKeyBits() << " bits" << htmlEndl; break; case EKT_EC: ecname = cert->getCertCurveName(); if (ecname.length() > 0) { os << "EC, curve " << ecname << ", " << cert->getCertKeyBits() << " bits"; } else { os << "EC, " << cert->getCertKeyBits() << " bits"; } break; case EKT_DH: os << "DH, " << cert->getCertKeyBits() << " bits" << htmlEndl; break; case EKT_none: // should never happen os << "none" << htmlEndl; break; } } if (sicond.good()) { os << htmlVfyOK << "Verification: OK" << htmlEndl; } else if (sicond == SI_EC_VerificationFailed_NoTrust) { untrustworthy_counter++; os << htmlVfyCA << "Verification: Signature is valid but certificate could not be verified: " << certVerifier.lastError() << "" << htmlEndl ; } else { corrupt_counter++; os << htmlVfyErr << "Verification: "; os << sicond.text() << "" << htmlEndl; } os << htmlTableE; } } signer.detach(); sigItem = DcmSignature::findNextSignatureItem(dataset, stack); } #endif switch (objtype) { case DVPSS_structuredReport: if (counter == 0) os << "The current structured report does not contain any digital signature." << OFendl; corruptSignaturesSR = corrupt_counter; untrustSignaturesSR = untrustworthy_counter; correctSignaturesSR = counter - corrupt_counter - untrustworthy_counter; break; case DVPSS_image: if (counter == 0) os << "The current image does not contain any digital signature." << OFendl; corruptSignaturesImage = corrupt_counter; untrustSignaturesImage = untrustworthy_counter; correctSignaturesImage = counter - corrupt_counter - untrustworthy_counter; break; case DVPSS_presentationState: if (counter == 0) os << "The current presentation state does not contain any digital signature." << OFendl; corruptSignaturesPState = corrupt_counter; untrustSignaturesPState = untrustworthy_counter; correctSignaturesPState = counter - corrupt_counter - untrustworthy_counter; break; } os << htmlFoot << OFStringStream_ends; OFSTRINGSTREAM_GETSTR(os, newText) replaceString(objtype, newText); // copies newText into OFString OFSTRINGSTREAM_FREESTR(newText) updateSignatureValidationOverview(); return; } void DVSignatureHandler::disableDigitalSignatureInformation(DVPSObjectType objtype) { switch (objtype) { case DVPSS_structuredReport: htmlSR = "Structured ReportThe current structured report does not contain any digital signature.\n"; corruptSignaturesSR = 0; untrustSignaturesSR = 0; correctSignaturesSR = 0; break; case DVPSS_image: corruptSignaturesImage = 0; untrustSignaturesImage = 0; correctSignaturesImage = 0; htmlImage = "ImageThe current image does not contain any digital signature.\n"; break; case DVPSS_presentationState: corruptSignaturesPState = 0; untrustSignaturesPState = 0; correctSignaturesPState = 0; htmlPState = "Presentation StateThe current presentation state does not contain any digital signature.\n"; break; } updateSignatureValidationOverview(); } void DVSignatureHandler::disableImageAndPState() { corruptSignaturesImage = 0; untrustSignaturesImage = 0; correctSignaturesImage = 0; htmlImage = "ImageNo image is currently active.\n"; corruptSignaturesPState = 0; untrustSignaturesPState = 0; correctSignaturesPState = 0; htmlPState = "Presentation StateNo presentation state is currently active.\n"; updateSignatureValidationOverview(); return; } DVPSSignatureStatus DVSignatureHandler::getCurrentSignatureStatus(DVPSObjectType objtype) const { switch (objtype) { case DVPSS_structuredReport: if ((correctSignaturesSR + corruptSignaturesSR + untrustSignaturesSR) == 0) return DVPSW_unsigned; if ((corruptSignaturesSR + untrustSignaturesSR) == 0) return DVPSW_signed_OK; if (corruptSignaturesSR == 0) return DVPSW_signed_unknownCA; break; case DVPSS_image: if ((correctSignaturesImage + corruptSignaturesImage + untrustSignaturesImage) == 0) return DVPSW_unsigned; if ((corruptSignaturesImage + untrustSignaturesImage) == 0) return DVPSW_signed_OK; if (corruptSignaturesImage == 0) return DVPSW_signed_unknownCA; break; case DVPSS_presentationState: if ((correctSignaturesPState + corruptSignaturesPState + untrustSignaturesPState) == 0) return DVPSW_unsigned; if ((corruptSignaturesPState + untrustSignaturesPState) == 0) return DVPSW_signed_OK; if (corruptSignaturesPState == 0) return DVPSW_signed_unknownCA; break; } return DVPSW_signed_corrupt; } DVPSSignatureStatus DVSignatureHandler::getCombinedImagePStateSignatureStatus() const { DVPSSignatureStatus statImage = getCurrentSignatureStatus(DVPSS_image); DVPSSignatureStatus statPState = getCurrentSignatureStatus(DVPSS_presentationState); if ((statImage == DVPSW_signed_corrupt)||(statPState == DVPSW_signed_corrupt)) return DVPSW_signed_corrupt; if ((statImage == DVPSW_signed_unknownCA)||(statPState == DVPSW_signed_unknownCA)) return DVPSW_signed_unknownCA; if ((statImage == DVPSW_signed_OK)&&(statPState == DVPSW_signed_OK)) return DVPSW_signed_OK; return DVPSW_unsigned; } const char *DVSignatureHandler::getCurrentSignatureValidationHTML(DVPSObjectType objtype) const { const char *result = ""; switch (objtype) { case DVPSS_structuredReport: result = htmlSR.c_str(); break; case DVPSS_image: result = htmlImage.c_str(); break; case DVPSS_presentationState: result = htmlPState.c_str(); break; } return result; } void DVSignatureHandler::updateSignatureValidationOverview() { const char *htmlHead = "\nOverview\n"; const char *htmlFoot = "\n\n"; const char *htmlEndl = "\n"; const char *htmlTitle = ""; const char *htmlVfyUns = ""; const char *htmlVfySig = ""; const char *htmlVfyCA = ""; const char *htmlVfyErr = ""; const char *htmlLine1 = " "; const char *htmlNext = ""; const char *htmlTableUns = "

\n"; const char *htmlTableSig = "

\n"; const char *htmlTableCA = "

\n"; const char *htmlTableErr = "

\n"; const char *htmlTableE = "

\n\n"; OFOStringStream os; DVPSSignatureStatus status; os << htmlHead; // Structured Report status = getCurrentSignatureStatus(DVPSS_structuredReport); switch (status) { case DVPSW_unsigned: os << htmlTableUns; break; case DVPSW_signed_OK: os << htmlTableSig; break; case DVPSW_signed_unknownCA: os << htmlVfyCA; break; case DVPSW_signed_corrupt: os << htmlTableErr; break; } os << htmlTitle << "Structured Report"<< htmlEndl; os << htmlLine1 << "Number of correct signatures" << htmlNext << correctSignaturesSR << htmlEndl; os << htmlLine1 << "Number of corrupt signatures" << htmlNext << corruptSignaturesSR << htmlEndl; os << htmlLine1 << "Number of untrusted signatures" << htmlNext << untrustSignaturesSR << htmlEndl; switch (status) { case DVPSW_unsigned: os << htmlVfyUns << "Status: unsigned" << htmlEndl; break; case DVPSW_signed_OK: os << htmlVfySig << "Status: signed" << htmlEndl; break; case DVPSW_signed_unknownCA: os << htmlVfyCA << "Status: signed but untrustworthy: certificate could not be verified" << htmlEndl; break; case DVPSW_signed_corrupt: os << htmlVfyErr << "Status: contains corrupt signatures" << htmlEndl; break; } os << htmlTableE; // Image status = getCurrentSignatureStatus(DVPSS_image); switch (status) { case DVPSW_unsigned: os << htmlTableUns; break; case DVPSW_signed_OK: os << htmlTableSig; break; case DVPSW_signed_unknownCA: os << htmlTableCA; break; case DVPSW_signed_corrupt: os << htmlTableErr; break; } os << htmlTitle << "Image"<< htmlEndl; os << htmlLine1 << "Number of correct signatures" << htmlNext << correctSignaturesImage << htmlEndl; os << htmlLine1 << "Number of corrupt signatures" << htmlNext << corruptSignaturesImage << htmlEndl; os << htmlLine1 << "Number of untrusted signatures" << htmlNext << untrustSignaturesImage << htmlEndl; switch (status) { case DVPSW_unsigned: os << htmlVfyUns << "Status: unsigned" << htmlEndl; break; case DVPSW_signed_OK: os << htmlVfySig << "Status: signed" << htmlEndl; break; case DVPSW_signed_unknownCA: os << htmlVfyCA << "Status: signed but untrustworthy: certificate could not be verified" << htmlEndl; break; case DVPSW_signed_corrupt: os << htmlVfyErr << "Status: contains corrupt signatures" << htmlEndl; break; } os << htmlTableE; // Presentation State status = getCurrentSignatureStatus(DVPSS_presentationState); switch (status) { case DVPSW_unsigned: os << htmlTableUns; break; case DVPSW_signed_OK: os << htmlTableSig; break; case DVPSW_signed_unknownCA: os << htmlTableCA; break; case DVPSW_signed_corrupt: os << htmlTableErr; break; } os << htmlTitle << "Presentation State"<< htmlEndl; os << htmlLine1 << "Number of correct signatures" << htmlNext << correctSignaturesPState << htmlEndl; os << htmlLine1 << "Number of corrupt signatures" << htmlNext << corruptSignaturesPState << htmlEndl; os << htmlLine1 << "Number of untrusted signatures" << htmlNext << untrustSignaturesPState << htmlEndl; switch (status) { case DVPSW_unsigned: os << htmlVfyUns << "Status: unsigned" << htmlEndl; break; case DVPSW_signed_OK: os << htmlVfySig << "Status: signed" << htmlEndl; break; case DVPSW_signed_unknownCA: os << htmlVfyCA << "Status: signed but untrustworthy: certificate could not be verified" << htmlEndl; break; case DVPSW_signed_corrupt: os << htmlVfyErr << "Status: contains corrupt signatures" << htmlEndl; break; } os << htmlTableE; os << htmlFoot << OFStringStream_ends; OFSTRINGSTREAM_GETSTR(os, newText) htmlOverview = newText; OFSTRINGSTREAM_FREESTR(newText) return; } const char *DVSignatureHandler::getCurrentSignatureValidationOverview() const { return htmlOverview.c_str(); } unsigned long DVSignatureHandler::getNumberOfCorrectSignatures(DVPSObjectType objtype) const { unsigned long result = 0; switch (objtype) { case DVPSS_structuredReport: result = correctSignaturesSR; break; case DVPSS_image: result = correctSignaturesImage; break; case DVPSS_presentationState: result = correctSignaturesPState; break; } return result; } unsigned long DVSignatureHandler::getNumberOfUntrustworthySignatures(DVPSObjectType objtype) const { unsigned long result = 0; switch (objtype) { case DVPSS_structuredReport: result = untrustSignaturesSR; break; case DVPSS_image: result = untrustSignaturesImage; break; case DVPSS_presentationState: result = untrustSignaturesPState; break; } return result; } unsigned long DVSignatureHandler::getNumberOfCorruptSignatures(DVPSObjectType objtype) const { unsigned long result = 0; switch (objtype) { case DVPSS_structuredReport: result = corruptSignaturesSR; break; case DVPSS_image: result = corruptSignaturesImage; break; case DVPSS_presentationState: result = corruptSignaturesPState; break; } return result; } #ifdef WITH_OPENSSL OFBool DVSignatureHandler::attributesSigned(DcmItem& item, DcmAttributeTag& tagList) const { DcmStack stack; DcmAttributeTag at(DCM_DataElementsSigned); DcmTagKey tagkey; DcmSignature signer; unsigned long numSignatures; unsigned long l; DVSignatureHandlerSignatureProfile sigProfile(tagList); DcmItem *sigItem = DcmSignature::findFirstSignatureItem(item, stack); while (sigItem) { if (sigItem == &item) { // signatures on main level - check attributes signed signer.attach(sigItem); numSignatures = signer.numberOfSignatures(); for (l=0; l 1) // should always be true { DcmObject *obj = stack.elem(scard-2); if (obj) // should always be true { if (sigProfile.tagInList(obj->getTag())) return OFTrue; // one of the elements in tagList contains a signature } } } sigItem = DcmSignature::findNextSignatureItem(item, stack); } return OFFalse; } #else OFBool DVSignatureHandler::attributesSigned(DcmItem& /* item */, DcmAttributeTag& /* tagList */) const { return OFFalse; } #endif #ifdef WITH_OPENSSL OFCondition DVSignatureHandler::createSignature( DcmItem& mainDataset, const DcmStack& itemStack, DcmAttributeTag& attributesNotToSignInMainDataset, const char *userID, const char *passwd) { if (userID == NULL) return EC_IllegalCall; // get user settings int fileformat = config.getTLSPEMFormat() ? X509_FILETYPE_PEM : X509_FILETYPE_ASN1; const char *userDir = config.getUserCertificateFolder(); const char *userKey = config.getUserPrivateKey(userID); if (userKey == NULL) return EC_IllegalCall; const char *userCert = config.getUserCertificate(userID); if (userCert == NULL) return EC_IllegalCall; // load private key SiPrivateKey key; OFString filename; if (userDir) { filename = userDir; filename += PATH_SEPARATOR; } filename += userKey; if (passwd) key.setPrivateKeyPasswd(passwd); OFCondition sicond = key.loadPrivateKey(filename.c_str(), fileformat); if (sicond != EC_Normal) return EC_IllegalCall; // unable to load private key // load certificate SiCertificate cert; filename.clear(); if (userDir) { filename = userDir; filename += PATH_SEPARATOR; } filename += userCert; sicond = cert.loadCertificate(filename.c_str(), fileformat); if (sicond != EC_Normal) return EC_IllegalCall; // unable to load certificate if (! key.matchesCertificate(cert)) return EC_IllegalCall; // private key does not match certificate DcmSignature signer; SiMDMAC mac(EMT_RIPEMD160); SiNullProfile nullProfile; DVSignatureHandlerSignatureProfile mainProfile(attributesNotToSignInMainDataset); DcmObject *current; DcmItem *currentItem; DcmStack workStack(itemStack); while (! workStack.empty()) { current = workStack.pop(); if ((current->ident() != EVR_dataset) && (current->ident() != EVR_item)) return EC_IllegalCall; // wrong type on stack currentItem = (DcmItem *)current; signer.attach(currentItem); if (currentItem == &mainDataset) { // we're creating a signature in the main dataset // we have to establish an explicit tag list, otherwise the profile does not work! DcmAttributeTag tagList(DCM_DataElementsSigned); unsigned long numAttributes = currentItem->card(); for (unsigned long l=0; lgetElement(l)->getTag(),l); } sicond = signer.createSignature(key, cert, mac, mainProfile, EXS_LittleEndianExplicit, &tagList); if (sicond != EC_Normal) return EC_IllegalCall; // error while creating signature } else { // we're creating a signature in a sequence item sicond = signer.createSignature(key, cert, mac, nullProfile, EXS_LittleEndianExplicit); if (sicond != EC_Normal) return EC_IllegalCall; // error while creating signature } signer.detach(); } return EC_Normal; } #else OFCondition DVSignatureHandler::createSignature( DcmItem& /* mainDataset */, const DcmStack& /* itemStack */, DcmAttributeTag& /* attributesNotToSignInMainDataset */, const char * /* userID */, const char * /* passwd */) { return EC_IllegalCall; } #endif