/** * Searches certificate by subject and returns a copy of it if found. * If not found returns <code>NULL</code>. * NB! The returned certificate must be freed with OpenSSL function X509_free(X509* cert). * * @param subject certificate subject. * @return returns copy of found certificate or <code>NULL</code> if certificate was not found. * @throws IOException exception is thrown if copying certificate failed. */ X509Cert X509CertStore::findIssuer(const X509Cert &cert, const set<string> &type) const { activate(cert.issuerName("C")); SCOPE(AUTHORITY_KEYID, akid, X509_get_ext_d2i(cert.handle(), NID_authority_key_identifier, nullptr, nullptr)); for(const TSL::Service &s: *d) { if(type.find(s.type) == type.cend()) continue; for(const X509Cert &i: s.certs) { if(!akid || !akid->keyid) { if(X509_NAME_cmp(X509_get_subject_name(i.handle()), X509_get_issuer_name(cert.handle()))) return i; } else { SCOPE(ASN1_OCTET_STRING, skid, X509_get_ext_d2i(i.handle(), NID_subject_key_identifier, nullptr, nullptr)); if(skid.get() && ASN1_OCTET_STRING_cmp(akid->keyid, skid.get()) == 0) return i; } } } return X509Cert(); }
/** * Validate signature value. * * @throws throws exception if signature value did not match. */ void digidoc::SignatureBES::checkSignatureValue() const throw(SignatureException) { DEBUG("SignatureBES::checkSignatureValue()"); try { // Initialize RSA crypter. X509Cert cert = getSigningCertificate(); RSACrypt rsa(cert.handle()); // Calculate SHA digest of the Signature->SignedInfo node. std::auto_ptr<Digest> calc(new Digest(getSignatureMethod())); std::vector<unsigned char> sha = calcDigestOnNode(calc.get(), URI_ID_DSIG, "SignedInfo"); DEBUGMEM("Digest", &sha[0], sha.size()); // Get signature value. std::vector<unsigned char> signatureShaRsa = getSignatureValue(); // Verify signature value with public RSA key. bool valid = rsa.verify(calc->getMethod(), sha, signatureShaRsa); // Check that signature matched. if(!valid) { THROW_SIGNATUREEXCEPTION("Signature is not valid."); } } catch(const IOException& e) { THROW_SIGNATUREEXCEPTION_CAUSE(e, "Failed to validate signature."); } }
void TSL::validate(const std::vector<X509Cert> &certs) { if(!tsl) THROW("Failed to parse XML"); X509Cert signingCert; if(tsl->signature().present() && tsl->signature()->keyInfo().present() && !tsl->signature()->keyInfo()->x509Data().empty() && !tsl->signature()->keyInfo()->x509Data().front().x509Certificate().empty()) { const Base64Binary &base64 = tsl->signature()->keyInfo()->x509Data().front().x509Certificate().front(); signingCert = X509Cert((const unsigned char*)base64.data(), base64.capacity()); } if(find(certs.begin(), certs.end(), signingCert) == certs.end()) THROW("TSL Signature is signed with untrusted certificate"); try { XSECProvider prov; DSIGSignature *sig = prov.newSignatureFromDOM(tsl->_node()->getOwnerDocument()); //sig->setKeyInfoResolver(new XSECKeyInfoResolverDefault); sig->setSigningKey(OpenSSLCryptoX509(signingCert.handle()).clonePublicKey()); //sig->registerIdAttributeName(MAKE_UNICODE_STRING("ID")); sig->load(); if(!sig->verify()) { string msg = xsd::cxx::xml::transcode<char>(sig->getErrMsgs()); THROW("TLS Signature is invalid: %s", msg.c_str()); } } catch(XSECException &e) { string msg = xsd::cxx::xml::transcode<char>(e.getMsg()); THROW("TSL Signature is invalid: %s", msg.c_str()); } catch(const Exception &) { throw; } catch(...) { THROW("TSL Signature is invalid"); } }
Signature *DDoc::prepareSignature(Signer *signer) { d->throwDocOpenError( __LINE__ ); if(d->documents.empty()) THROW("No documents in container, can not sign container."); if(!signer) THROW("Null pointer in DDoc::sign"); X509Cert cert = signer->cert(); if(!cert) THROW("Failed to sign document, Certificate cannot be NULL"); ostringstream role; vector<string> r = signer->signerRoles(); for(vector<string>::const_iterator i = r.begin(); i != r.end(); ++i) { role << *i; if(i + 1 != r.end()) role << " / "; } #ifdef __APPLE__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif SignatureInfo *info = nullptr; int err = d->lib->f_ddocPrepareSignature(d->doc, &info, (role.str().empty() ? 0 : role.str().c_str()), (signer->city().empty() ? 0 : signer->city().c_str()), (signer->stateOrProvince().empty() ? 0 : signer->stateOrProvince().c_str()), (signer->postalCode().empty() ? 0 : signer->postalCode().c_str()), (signer->countryName().empty() ? 0 : signer->countryName().c_str()), X509_dup(cert.handle()), 0); #ifdef __APPLE__ #pragma GCC diagnostic pop #endif d->throwSignError( info, err, "Failed to sign document", __LINE__ ); if( !info ) d->throwCodeError(ERR_NULL_POINTER, "Failed to sign document", __LINE__); d->loadSignatures(); return d->signatures.back(); }
/** * Check if X509Cert is signed by trusted issuer * @throw Exception if error */ bool X509CertStore::verify(const X509Cert &cert, bool noqscd) const { activate(cert.issuerName("C")); const ASN1_TIME *asn1time = X509_get0_notBefore(cert.handle()); time_t time = util::date::ASN1TimeToTime_t(string((const char*)asn1time->data, size_t(asn1time->length)), asn1time->type == V_ASN1_GENERALIZEDTIME); SCOPE(X509_STORE, store, createStore(X509CertStore::CA, &time)); SCOPE(X509_STORE_CTX, csc, X509_STORE_CTX_new()); if(!X509_STORE_CTX_init(csc.get(), store.get(), cert.handle(), nullptr)) THROW_OPENSSLEXCEPTION("Failed to init X509_STORE_CTX"); if(X509_verify_cert(csc.get()) > 0) { if(noqscd) return true; const TSL::Validity *v = static_cast<const TSL::Validity*>(X509_STORE_CTX_get_ex_data(csc.get(), 0)); const vector<string> policies = cert.certificatePolicies(); const vector<string> qcstatement = cert.qcStatements(); const vector<X509Cert::KeyUsage> keyUsage = cert.keyUsage(); bool isQCCompliant = find(qcstatement.cbegin(), qcstatement.cend(), X509Cert::QC_COMPLIANT) != qcstatement.cend(); bool isQSCD = find(policies.cbegin(), policies.cend(), X509Cert::QCP_PUBLIC_WITH_SSCD) != policies.cend() || find(policies.cbegin(), policies.cend(), X509Cert::QCP_LEGAL_QSCD) != policies.cend() || find(policies.cbegin(), policies.cend(), X509Cert::QCP_NATURAL_QSCD) != policies.cend() || find(qcstatement.cbegin(), qcstatement.cend(), X509Cert::QC_SSCD) != qcstatement.cend(); bool isESeal = // Special treamtent for E-Seals find(policies.cbegin(), policies.cend(), X509Cert::QCP_LEGAL) != policies.cend() || find(qcstatement.cbegin(), qcstatement.cend(), X509Cert::QCT_ESEAL) != qcstatement.cend(); auto matchPolicySet = [&](const vector<string> &policySet){ return all_of(policySet.cbegin(), policySet.cend(), [&](const string &policy){ return find(policies.cbegin(), policies.cend(), policy) != policies.cend(); }); }; auto matchKeyUsageSet = [&](const map<X509Cert::KeyUsage,bool> &keyUsageSet){ return all_of(keyUsageSet.cbegin(), keyUsageSet.cend(), [&](pair<X509Cert::KeyUsage,bool> keyUsageBit){ return (find(keyUsage.cbegin(), keyUsage.cend(), keyUsageBit.first) != keyUsage.cend()) == keyUsageBit.second; }); }; for(const TSL::Qualifier &q: v->qualifiers) { if(q.assert_ == "all") { if(!(all_of(q.policySet.cbegin(), q.policySet.cend(), matchPolicySet) && all_of(q.keyUsage.cbegin(), q.keyUsage.cend(), matchKeyUsageSet))) continue; } else if(q.assert_ == "atLeastOne") { if(!(any_of(q.policySet.cbegin(), q.policySet.cend(), matchPolicySet) || any_of(q.keyUsage.cbegin(), q.keyUsage.cend(), matchKeyUsageSet) )) continue; } else { WARN("Unable to handle Qualifier assert '%s'", q.assert_.c_str()); continue; } for(const string &qc: q.qualifiers) { if(qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCStatement" || qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCForESig") isQCCompliant = true; else if(qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/NotQualified") isQCCompliant = false; else if(qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCSSCDStatusAsInCert" || qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCQSCDStatusAsInCert") continue; else if(qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCWithSSCD" || qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCWithQSCD") isQSCD = true; else if(qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCNoSSCD" || qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCNoQSCD") isQSCD = false; else if(qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCForLegalPerson" || qc == "http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/QCForESeal") isESeal = true; } } if(!((isQCCompliant && isQSCD) || isESeal)) { Exception e(EXCEPTION_PARAMS("Signing certificate does not meet Qualification requirements")); e.setCode(Exception::CertificateIssuerMissing); throw e; } return true; } int err = X509_STORE_CTX_get_error(csc.get()); Exception e(EXCEPTION_PARAMS(X509_verify_cert_error_string(err)), OpenSSLException()); switch(err) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: e.setCode(Exception::CertificateIssuerMissing); throw e; default: throw e; } }
Connect::Connect(const string &_url, const string &method, int timeout, const string &useragent, const X509Cert &cert) : _timeout(timeout) { DEBUG("Connecting to URL: %s", _url.c_str()); char *_host = nullptr, *_port = nullptr, *_path = nullptr; int usessl = 0; if(!OCSP_parse_url(const_cast<char*>(_url.c_str()), &_host, &_port, &_path, &usessl)) THROW_OPENSSLEXCEPTION("Incorrect URL provided: '%s'.", _url.c_str()); string host = _host ? _host : ""; string port = _port ? _port : "80"; string path = _path ? _path : "/"; string url = (_path && strlen(_path) == 1 && _path[0] == '/' && _url[_url.size() - 1] != '/') ? _url + "/" : _url; OPENSSL_free(_host); OPENSSL_free(_port); OPENSSL_free(_path); string hostname = host + ":" + port; Conf *c = Conf::instance(); if(!c->proxyHost().empty() && (usessl == 0 || (CONF(proxyForceSSL)) || (CONF(proxyTunnelSSL)))) { hostname = c->proxyHost() + ":" + c->proxyPort(); path = url; } DEBUG("Connecting to Host: %s timeout: %i", hostname.c_str(), _timeout); d = BIO_new_connect(const_cast<char*>(hostname.c_str())); if(!d) THROW_OPENSSLEXCEPTION("Failed to create connection with host: '%s'", hostname.c_str()); BIO_set_nbio(d, _timeout > 0); auto start = chrono::high_resolution_clock::now(); while(BIO_do_connect(d) != 1) { if(_timeout == 0) THROW_OPENSSLEXCEPTION("Failed to connect to host: '%s'", hostname.c_str()); if(!BIO_should_retry(d)) THROW_OPENSSLEXCEPTION("Failed to connect to host: '%s'", hostname.c_str()); auto end = chrono::high_resolution_clock::now(); if(chrono::duration_cast<chrono::seconds>(end - start).count() >= _timeout) THROW("Failed to create connection with host timeout: '%s'", hostname.c_str()); this_thread::sleep_for(chrono::milliseconds(50)); } if(usessl > 0) { if(!c->proxyHost().empty() && (CONF(proxyTunnelSSL))) { BIO_printf(d, "CONNECT %s:%s HTTP/1.0\r\n", host.c_str(), port.c_str()); addHeader("Host", host + ":" + port); sendProxyAuth(); _timeout = 1; // Don't wait additional data on read, case proxy tunnel Result r = exec(); if(!r.isOK() || r.result.find("established") == string::npos) THROW_OPENSSLEXCEPTION("Failed to create proxy connection with host: '%s'", hostname.c_str()); _timeout = timeout; // Restore } ssl.reset(SSL_CTX_new(SSLv23_client_method()), SSL_CTX_free); if(!ssl) THROW_OPENSSLEXCEPTION("Failed to create ssl connection with host: '%s'", hostname.c_str()); SSL_CTX_set_mode(ssl.get(), SSL_MODE_AUTO_RETRY); SSL_CTX_set_quiet_shutdown(ssl.get(), 1); if(cert.handle()) { SSL_CTX_set_verify(ssl.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); SSL_CTX_set_cert_verify_callback(ssl.get(), [](X509_STORE_CTX *store, void *cert) -> int { X509 *x509 = X509_STORE_CTX_get0_cert(store); return x509 && X509_cmp(x509, (X509*)cert) == 0 ? 1 : 0; }, cert.handle()); } BIO *sbio = BIO_new_ssl(ssl.get(), 1); if(!sbio) THROW_OPENSSLEXCEPTION("Failed to create ssl connection with host: '%s'", hostname.c_str()); d = BIO_push(sbio, d); while(BIO_do_handshake(d) != 1) { if(_timeout == 0) THROW("Failed to create ssl connection with host: '%s'", hostname.c_str()); auto end = chrono::high_resolution_clock::now(); if(chrono::duration_cast<chrono::seconds>(end - start).count() >= _timeout) THROW("Failed to create ssl connection with host timeout: '%s'", hostname.c_str()); this_thread::sleep_for(chrono::milliseconds(50)); } } BIO_printf(d, "%s %s HTTP/1.0\r\n", method.c_str(), path.c_str()); if(port == "80") addHeader("Host", host); else addHeader("Host", host + ":" + port); addHeader("User-Agent", "LIB libdigidocpp/" + string(VER_STR(MAJOR_VER.MINOR_VER.RELEASE_VER.BUILD_VER)) + " APP " + appInfo() + useragent); if(usessl == 0) sendProxyAuth(); }