/******************************************************************************* * libretroshare/src/pqi: sslfns.cc * * * * libretroshare: retroshare core library * * * * Copyright 2004-2008 by Robert Fernie * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this program. If not, see . * * * *******************************************************************************/ /* Functions in this file are SSL only, * and have no dependence on SSLRoot() etc. * might need SSL_Init() to be called - thats it! */ /******************** notify of new Cert **************************/ #include "pqi/sslfns.h" #include "pqi/pqi_base.h" #include "util/rsdir.h" #include "util/rsstring.h" #include "pqi/authssl.h" #include #include #include #include #include #include #include /**** * #define AUTHSSL_DEBUG 1 ***/ /********************************************************************************/ #if defined(SSLFNS_ADD_CIPHER_CTX_RAND_KEY) int EVP_CIPHER_CTX_rand_key(EVP_CIPHER_CTX *ctx, unsigned char *key) { //if (ctx->cipher->flags & EVP_CIPH_RAND_KEY) // return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_RAND_KEY, 0, key); if (RAND_bytes(key, ctx->key_len) <= 0) return 0; return 1; } #endif /********************************************************************************/ /********************************************************************************/ X509_REQ *GenerateX509Req( std::string pkey_file, std::string passwd, std::string name, std::string email, std::string org, std::string loc, std::string state, std::string country, int nbits_in, std::string &errString) { /* generate request */ X509_REQ *req=X509_REQ_new(); // setup output. BIO *bio_out = BIO_new(BIO_s_file()); BIO_set_fp(bio_out,stdout,BIO_NOCLOSE); EVP_PKEY *pkey = NULL; X509_NAME *x509_name = NULL ; RSA *rsa = NULL ; try { // first generate a key.... if ((pkey=EVP_PKEY_new()) == NULL) { fprintf(stderr,"GenerateX509Req: Couldn't Create Key\n"); throw std::runtime_error("Couldn't Create Key") ; } int nbits = 2048; unsigned long e = 0x10001; if ((nbits_in >= 512) && (nbits_in <= 4096)) { nbits = nbits_in; } else { fprintf(stderr,"GenerateX509Req: strange num of nbits: %d\n", nbits_in); fprintf(stderr,"GenerateX509Req: reverting to %d\n", nbits); } rsa = RSA_new(); if ((rsa == NULL) || !EVP_PKEY_assign_RSA(pkey, rsa)) throw std::runtime_error("Couldn't generate RSA Key"); BIGNUM *ebn = BN_new(); BN_set_word(ebn, e); RSA_generate_key_ex(rsa, nbits, ebn, NULL); // open the file. FILE *out; if (NULL == (out = RsDirUtil::rs_fopen(pkey_file.c_str(), "w"))) throw std::runtime_error("Couldn't Create Key File \"" + pkey_file + "\""); const EVP_CIPHER *cipher = EVP_des_ede3_cbc(); if (!PEM_write_PrivateKey(out,pkey,cipher, NULL,0,NULL,(void *) passwd.c_str())) { fclose(out) ; throw std::runtime_error("Couldn't Save Private Key to file \""+pkey_file+"\""); } fclose(out); // We have now created a private key.... std::cerr << "GenerateX509Req() Saved Private Key to file \"" << pkey_file << "\"" << std::endl; /********** Test Loading the private Key.... ************/ FILE *tst_in = NULL; EVP_PKEY *tst_pkey = NULL; if (NULL == (tst_in = RsDirUtil::rs_fopen(pkey_file.c_str(), "rb"))) throw std::runtime_error("GenerateX509Req() Couldn't Open Private Key file \""+pkey_file+"\"") ; if (NULL == (tst_pkey = PEM_read_PrivateKey(tst_in,NULL,NULL,(void *) passwd.c_str()))) { fclose(tst_in); throw std::runtime_error("GenerateX509Req() Couldn't read Private Key file \""+pkey_file+"\"") ; } fclose(tst_in); EVP_PKEY_free(tst_pkey); /* Fill in details: fields. req->req_info; req->req_info->enc; req->req_info->version; req->req_info->subject; req->req_info->pubkey; ****************************/ long version = 0x00; unsigned long chtype = MBSTRING_UTF8; x509_name = X509_NAME_new(); // fill in the request. /**** X509_REQ -> Version ********************************/ if(!X509_REQ_set_version(req,version)) /* version 1 */ throw std::runtime_error("GenerateX509Req(): Couldn't Set SSL certificate Version!"); /**** X509_REQ -> Version ********************************/ /**** X509_REQ -> Key ********************************/ if (!X509_REQ_set_pubkey(req,pkey)) throw std::runtime_error("GenerateX509Req(): Couldn't Set SSL certificate PUBKEY!"); /**** SUBJECT ********************************/ // create the name. // fields to add. // commonName CN // emailAddress (none) // organizationName O // localityName L // stateOrProvinceName ST // countryName C if (0 == strlen(name.c_str())) throw std::runtime_error("No name! Aborting.") ; X509_NAME_add_entry_by_txt(x509_name, "CN", chtype, (unsigned char *) name.c_str(), -1, -1, 0); if (0 < strlen(email.c_str())) X509_NAME_add_entry_by_NID(x509_name, 48, 0, (unsigned char *) email.c_str(), -1, -1, 0); if (0 < strlen(org.c_str())) X509_NAME_add_entry_by_txt(x509_name, "O", chtype, (unsigned char *) org.c_str(), -1, -1, 0); if (0 < strlen(loc.c_str())) X509_NAME_add_entry_by_txt(x509_name, "L", chtype, (unsigned char *) loc.c_str(), -1, -1, 0); if (0 < strlen(state.c_str())) X509_NAME_add_entry_by_txt(x509_name, "ST", chtype, (unsigned char *) state.c_str(), -1, -1, 0); if (0 < strlen(country.c_str())) X509_NAME_add_entry_by_txt(x509_name, "C", chtype, (unsigned char *) country.c_str(), -1, -1, 0); if (!X509_REQ_set_subject_name(req,x509_name)) throw std::runtime_error("GenerateX509Req() Couldn't Set Name to Request!"); X509_NAME_free(x509_name); /**** SUBJECT ********************************/ if (!X509_REQ_sign(req,pkey,EVP_sha1())) throw std::runtime_error("GenerateX509Req() Failed to sign REQ"); errString = "No Error"; return req; } catch(std::exception& e) { std::cerr << "(EE) Key creation failed: " << e.what() << std::endl; errString = e.what() ; req = NULL ; } if(rsa) RSA_free(rsa); if(x509_name) X509_NAME_free(x509_name); if(bio_out) BIO_free_all(bio_out) ; if(pkey) EVP_PKEY_free(pkey); return req ; } #define SERIAL_RAND_BITS 64 #ifdef UNUSED_CODE X509 *SignX509Certificate(X509_NAME *issuer, EVP_PKEY *privkey, X509_REQ *req, long days) { const EVP_MD *digest = EVP_sha1(); ASN1_INTEGER *serial = ASN1_INTEGER_new(); EVP_PKEY *tmppkey; X509 *x509 = X509_new(); if (x509 == NULL) return NULL; BIGNUM *btmp = BN_new(); if (!BN_pseudo_rand(btmp, SERIAL_RAND_BITS, 0, 0)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," pseudo_rand\n"); return NULL; } if (!BN_to_ASN1_INTEGER(btmp, serial)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," int\n"); return NULL; } BN_free(btmp); if (!X509_set_serialNumber(x509, serial)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," serialNumber\n"); return NULL; } ASN1_INTEGER_free(serial); if (!X509_set_issuer_name(x509, issuer)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," issuer\n"); return NULL; } if (!X509_gmtime_adj(x509->cert_info->validity->notBefore, 0)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," notBefore\n"); return NULL; } //x509->cert_info->validity->notAfter //if (!X509_gmtime_adj(X509_get_notAfter(x509), (long)60*60*24*days)) if (!X509_gmtime_adj(x509->cert_info->validity->notAfter, (long)60*60*24*days)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," notAfter\n"); return NULL; } if (!X509_set_subject_name(x509, X509_REQ_get_subject_name(req))) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," subject_name\n"); return NULL; } tmppkey = X509_REQ_get_pubkey(req); if (!tmppkey || !X509_set_pubkey(x509,tmppkey)) { fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," pubkey\n"); return NULL; } /* Cleanup Algorithm part */ X509_ALGOR *algor1 = x509->cert_info->signature; X509_ALGOR *algor2 = x509->sig_alg; X509_ALGOR *a; a = algor1; ASN1_TYPE_free(a->parameter); a->parameter=ASN1_TYPE_new(); a->parameter->type=V_ASN1_NULL; ASN1_OBJECT_free(a->algorithm); a->algorithm=OBJ_nid2obj(digest->pkey_type); a = algor2; ASN1_TYPE_free(a->parameter); a->parameter=ASN1_TYPE_new(); a->parameter->type=V_ASN1_NULL; ASN1_OBJECT_free(a->algorithm); a->algorithm=OBJ_nid2obj(digest->pkey_type); if (!X509_sign(x509,privkey,digest)) { long e = ERR_get_error(); fprintf(stderr,"SignX509Certificate() Failed: "); fprintf(stderr," signing Error: %ld\n", e); fprintf(stderr,"ERR: %s, %s, %s\n", ERR_lib_error_string(e), ERR_func_error_string(e), ERR_reason_error_string(e)); int inl=i2d_X509(x509,NULL); int outl=EVP_PKEY_size(privkey); fprintf(stderr,"Size Check: inl: %d, outl: %d\n", inl, outl); return NULL; } fprintf(stderr,"SignX509Certificate() Success\n"); return x509; } #endif /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /* Sign / Encrypt / Verify Data */ bool SSL_SignDataBin(const void *data, const uint32_t len, unsigned char *sign, unsigned int *signlen, EVP_PKEY *pkey) { EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); unsigned int req_signlen = EVP_PKEY_size(pkey); if (req_signlen > *signlen) { /* not enough space */ std::cerr << "SignDataBin() Not Enough Sign SpacegnInit Failure!" << std::endl; return false; } if (0 == EVP_SignInit(mdctx, EVP_sha1())) { std::cerr << "EVP_SignInit Failure!" << std::endl; EVP_MD_CTX_destroy(mdctx); return false; } if (0 == EVP_SignUpdate(mdctx, data, len)) { std::cerr << "EVP_SignUpdate Failure!" << std::endl; EVP_MD_CTX_destroy(mdctx); return false; } if (0 == EVP_SignFinal(mdctx, sign, signlen, pkey)) { std::cerr << "EVP_SignFinal Failure!" << std::endl; EVP_MD_CTX_destroy(mdctx); return false; } EVP_MD_CTX_destroy(mdctx); return true; } bool SSL_VerifySignBin(const void *data, const uint32_t len, unsigned char *sign, unsigned int signlen, X509 *cert) { /* cert->cert_info->key->pkey is NULL.... * but is instantiated when we call X509_get_pubkey() */ EVP_PKEY *peerkey = X509_get_pubkey(cert); /* must free this key afterwards */ bool ret = SSL_VerifySignBin(data, len, sign, signlen, peerkey); EVP_PKEY_free(peerkey); return ret; } /* Correct form of this function ... Internal for AuthSSL's usage */ bool SSL_VerifySignBin(const void *data, const uint32_t len, unsigned char *sign, unsigned int signlen, EVP_PKEY *peerkey) { if(peerkey == NULL) { std::cerr << "VerifySignBin: no public key available !!" << std::endl ; return false ; } EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); if (0 == EVP_VerifyInit(mdctx, EVP_sha1())) { std::cerr << "EVP_VerifyInit Failure!" << std::endl; EVP_MD_CTX_destroy(mdctx); return false; } if (0 == EVP_VerifyUpdate(mdctx, data, len)) { std::cerr << "EVP_VerifyUpdate Failure!" << std::endl; EVP_MD_CTX_destroy(mdctx); return false; } if(signlen == 0 || sign == NULL) { std::cerr << "AuthSSL::VerifySignBin: signlen=" << signlen << ", sign=" << (void*)sign << "!!" << std::endl ; EVP_MD_CTX_destroy(mdctx); return false ; } // WARNING: EVP_SignFinal() may return -1 for some errors (instead of 0 when verification fails), // so successful signature verification only occurs when it returns 1. if (1 == EVP_VerifyFinal(mdctx, sign, signlen, peerkey)) { EVP_MD_CTX_destroy(mdctx); return true; } else { std::cerr << "EVP_VerifyFinal Failure!" << std::endl; EVP_MD_CTX_destroy(mdctx); return false; } } /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ X509 *loadX509FromPEM(std::string pem) { #ifdef AUTHSSL_DEBUG std::cerr << "loadX509FromPEM()"; std::cerr << std::endl; #endif /* Put the data into a mem BIO */ char *certstr = strdup(pem.c_str()); BIO *bp = BIO_new_mem_buf(certstr, -1); X509 *pc = PEM_read_bio_X509(bp, NULL, NULL, NULL); BIO_free(bp); free(certstr); return pc; } std::string saveX509ToPEM(X509* x509) { #ifdef AUTHSSL_DEBUG std::cerr << "saveX509ToPEM() " << std::endl; #endif /* get the cert first */ std::string certstr; BIO *bp = BIO_new(BIO_s_mem()); PEM_write_bio_X509(bp, x509); /* translate the bp data to a string */ char *data; int len = BIO_get_mem_data(bp, &data); for(int i = 0; i < len; i++) { certstr += data[i]; } BIO_free(bp); return certstr; } X509 *loadX509FromDER(const uint8_t *ptr, uint32_t len) { #ifdef AUTHSSL_DEBUG std::cerr << "AuthSSL::LoadX509FromDER()"; std::cerr << std::endl; #endif X509 *tmp = NULL; #ifdef __APPLE__ // This depends on which version you are compiling for... OSX10.5 doesn't have consts (old OpenSSL!) // With the Advent of Openpgpsdk, we are forced to move to OSX10.6 for a newer OpenSSL (probably a good thing). // So we can change this around. //unsigned char **certptr = (unsigned char **) &ptr; const unsigned char **certptr = (const unsigned char **) &ptr; #else const unsigned char **certptr = (const unsigned char **) &ptr; #endif X509 *x509 = d2i_X509(&tmp, certptr, len); return x509; } bool saveX509ToDER(X509 *x509, uint8_t **ptr, uint32_t *len) { #ifdef AUTHSSL_DEBUG std::cerr << "AuthSSL::saveX509ToDER()"; std::cerr << std::endl; #endif int certlen = i2d_X509(x509, (unsigned char **) ptr); if (certlen > 0) { *len = certlen; return true; } else { *len = 0; return false; } return false; } bool getX509id(X509 *x509, RsPeerId& xid) { #ifdef AUTHSSL_DEBUG std::cerr << "AuthSSL::getX509id()"; std::cerr << std::endl; #endif xid.clear() ; if (x509 == NULL) { #ifdef AUTHSSL_DEBUG std::cerr << "AuthSSL::getX509id() NULL pointer"; std::cerr << std::endl; #endif return false; } // get the signature from the cert, and copy to the array. #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) ASN1_BIT_STRING *signature = x509->signature; #else const ASN1_BIT_STRING *signature = NULL ; const X509_ALGOR *algor ; X509_get0_signature(&signature,&algor,x509); #endif uint32_t version_number = getX509RetroshareCertificateVersion(x509) ; if(version_number >= RS_CERTIFICATE_VERSION_NUMBER_06_0001) { // What: Computes the node id by performing a sha256 hash of the certificate's PGP signature, instead of simply picking up the last 20 bytes of it. // // Why: There is no real risk in forging a certificate with the same ID as the authentication is performed over the PGP signature of the certificate // which hashes the full SSL certificate (i.e. the full serialized CERT_INFO structure). However the possibility to // create two certificates with the same IDs is a problem, as it can be used to cause disturbance in the software. // // Backward compat: makes connexions impossible with non patched peers, probably because the SSL id that is computed is not the same on both side, // and in particular unpatched peers see a cerficate with ID different (because computed with the old method) than the ID that was // submitted when making friends. // // Note: the advantage of basing the ID on the signature rather than the public key is not very clear, given that the signature is based on a hash // of the public key (and the rest of the certificate info). // if(RsPeerId::SIZE_IN_BYTES > Sha256CheckSum::SIZE_IN_BYTES) return false ; xid = RsPeerId(RsDirUtil::sha256sum(ASN1_STRING_data(const_cast(signature)),ASN1_STRING_length(signature)).toByteArray()) ; } else { int signlen = ASN1_STRING_length(signature); if (signlen < CERTSIGNLEN) { #ifdef AUTHSSL_DEBUG std::cerr << "AuthSSL::getX509id() ERROR: Short Signature"; std::cerr << std::endl; #endif return false; } // else copy in the first CERTSIGNLEN. unsigned char *signdata = ASN1_STRING_data(const_cast(signature)); /* switched to the other end of the signature. for * more randomness */ #warning csoler 2017-02-19: This is cryptographically horrible. We should hash the entire signature here! xid = RsPeerId(&signdata[signlen - CERTSIGNLEN]) ; } return true; } /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ /********************************************************************************/ int pem_passwd_cb(char *buf, int size, int rwflag, void *password) { /* remove unused parameter warnings */ (void) rwflag; strncpy(buf, (char *)(password), size); buf[size - 1] = '\0'; return(strlen(buf)); } uint64_t getX509SerialNumber(X509 *cert) { ASN1_INTEGER *serial = X509_get_serialNumber(cert); BIGNUM *btmp = ASN1_INTEGER_to_BN(serial, NULL); uint64_t res = BN_get_word(btmp) ; BN_free(btmp); return res ; } uint32_t getX509RetroshareCertificateVersion(X509 *cert) { // Because the serial number was totally random before being used to identity the handshake protocol, we check if we see known version strings. If not, // we assume v0.6-0000 // // We compare the uint32_t into a uint64_t on purpose,to make sure that the highest bits are 0 and not random. switch(getX509SerialNumber(cert)) { case uint64_t(RS_CERTIFICATE_VERSION_NUMBER_06_0000): return RS_CERTIFICATE_VERSION_NUMBER_06_0000 ; case uint64_t(RS_CERTIFICATE_VERSION_NUMBER_06_0001): return RS_CERTIFICATE_VERSION_NUMBER_06_0001 ; case uint64_t(RS_CERTIFICATE_VERSION_NUMBER_07_0001): return RS_CERTIFICATE_VERSION_NUMBER_07_0001 ; default: return RS_CERTIFICATE_VERSION_NUMBER_06_0000; } } std::string getX509NameString(X509_NAME *name) { std::string namestr; for(int i = 0; i < X509_NAME_entry_count(name); i++) { X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); ASN1_STRING *entry_data = X509_NAME_ENTRY_get_data(entry); ASN1_OBJECT *entry_obj = X509_NAME_ENTRY_get_object(entry); namestr += "\t"; namestr += OBJ_nid2ln(OBJ_obj2nid(entry_obj)); namestr += " : "; //namestr += entry_obj -> flags; //namestr += entry_data -> length; //namestr += entry_data -> type; //namestr += entry_data -> flags; //entry -> set; if (entry_data -> data != NULL) { namestr += (char *) entry_data -> data; } else { namestr += "NULL"; } if (i + 1 < X509_NAME_entry_count(name)) { namestr += "\n"; } } return namestr; } std::string getX509CNString(X509_NAME *name) { std::string namestr; for(int i = 0; i < X509_NAME_entry_count(name); i++) { X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); ASN1_STRING *entry_data = X509_NAME_ENTRY_get_data(entry); ASN1_OBJECT *entry_obj = X509_NAME_ENTRY_get_object(entry); if (0 == strncmp("CN", OBJ_nid2sn(OBJ_obj2nid(entry_obj)), 2)) { if (entry_data -> data != NULL) { // do we need to convert ASN1_STRING to utf8 (ASN1_STRING_to_UTF8)? namestr = (char *) entry_data->data; } else { namestr = "Unknown"; } break; } } return namestr; } std::string getX509TypeString(X509_NAME *name, const char *type, int len) { std::string namestr; for(int i = 0; i < X509_NAME_entry_count(name); i++) { X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); ASN1_STRING *entry_data = X509_NAME_ENTRY_get_data(entry); ASN1_OBJECT *entry_obj = X509_NAME_ENTRY_get_object(entry); if (0 == strncmp(type, OBJ_nid2sn(OBJ_obj2nid(entry_obj)), len)) { if (entry_data -> data != NULL) { // do we need to convert ASN1_STRING to utf8 (ASN1_STRING_to_UTF8)? namestr = (char *) entry_data -> data; } else { namestr = "Unknown"; } break; } } return namestr; } std::string getX509LocString(X509_NAME *name) { return getX509TypeString(name, "L", 2); } std::string getX509OrgString(X509_NAME *name) { return getX509TypeString(name, "O", 2); } std::string getX509CountryString(X509_NAME *name) { return getX509TypeString(name, "C", 2); } #ifdef AUTHSSL_DEBUG std::string getX509Info(X509 *cert) { std::string out = "X509 Certificate:\n"; long l; l=X509_get_version(cert); rs_sprintf_append(out, " Version: %ld(0x%ld)\n", l+1, l); out += " Subject: \n"; out += " " + getX509NameString(cert->cert_info->subject) + "\n\n"; out += " Signatures:\n"; return out; } #endif /********** SSL ERROR STUFF ******************************************/ int printSSLError( SSL*, int retval, int err, unsigned long err2, std::string& out ) { std::string reason; std::string mainreason = std::string("UNKNOWN ERROR CODE"); if (err == SSL_ERROR_NONE) { mainreason = std::string("SSL_ERROR_NONE"); } else if (err == SSL_ERROR_ZERO_RETURN) { mainreason = std::string("SSL_ERROR_ZERO_RETURN"); } else if (err == SSL_ERROR_WANT_READ) { mainreason = std::string("SSL_ERROR_WANT_READ"); } else if (err == SSL_ERROR_WANT_WRITE) { mainreason = std::string("SSL_ERROR_WANT_WRITE"); } else if (err == SSL_ERROR_WANT_CONNECT) { mainreason = std::string("SSL_ERROR_WANT_CONNECT"); } else if (err == SSL_ERROR_WANT_ACCEPT) { mainreason = std::string("SSL_ERROR_WANT_ACCEPT"); } else if (err == SSL_ERROR_WANT_X509_LOOKUP) { mainreason = std::string("SSL_ERROR_WANT_X509_LOOKUP"); } else if (err == SSL_ERROR_SYSCALL) { mainreason = std::string("SSL_ERROR_SYSCALL"); } else if (err == SSL_ERROR_SSL) { mainreason = std::string("SSL_ERROR_SSL"); } rs_sprintf_append( out, "RetVal(%d) -> SSL Error: %s\n\t + ERR Error: %s\n", retval, mainreason.c_str(), ERR_error_string(err2, nullptr) ); return 1; } std::string sslErrorToString(int retval, int err, unsigned long err2) { std::string ret; // When printSSLError will be removed it's code will be moved here printSSLError(nullptr, retval, err, err2, ret); return ret; }