/*========================================================================= Program: GDCM (Grassroots DICOM). A DICOM library Copyright (c) 2006-2011 Mathieu Malaterre All rights reserved. See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ //#include "gdcmCryptographicMessageSyntax.h" #include "gdcmOpenSSLP7CryptographicMessageSyntax.h" #include "gdcmTrace.h" #include // numeric_limits #include // stderr #include // strcmp #include #include // time() #ifdef GDCM_USE_SYSTEM_OPENSSL #include #include #include #include #include #endif /* * http://en.wikipedia.org/wiki/PKCS * PKCS#7 <=> Cryptographic Message Syntax Standard */ namespace gdcm { /* * openssl genrsa -out CA_key.pem 2048 * * openssl req -new -key CA_key.pem -x509 -days 365 -out CA_cert.cer */ /* * openssl smime -encrypt -aes256 -in inputfile.txt -out outputfile.txt -outform DER /tmp/server.pem */ #ifdef GDCM_USE_SYSTEM_OPENSSL const EVP_CIPHER *CreateCipher( CryptographicMessageSyntax::CipherTypes ciphertype) { const EVP_CIPHER *cipher = 0; switch( ciphertype ) { case CryptographicMessageSyntax::DES3_CIPHER: // Triple DES cipher = EVP_des_ede3_cbc(); break; case CryptographicMessageSyntax::AES128_CIPHER: // CBC AES cipher = EVP_aes_128_cbc(); break; case CryptographicMessageSyntax::AES192_CIPHER: // ' ' cipher = EVP_aes_192_cbc(); break; case CryptographicMessageSyntax::AES256_CIPHER: // ' ' cipher = EVP_aes_256_cbc(); break; } return cipher; } #endif class CryptographicMessageSyntaxInternals { #ifdef GDCM_USE_SYSTEM_OPENSSL public: CryptographicMessageSyntaxInternals():recips(NULL),pkey(NULL),CipherType( CryptographicMessageSyntax::AES256_CIPHER ),cipher(NULL),p7(PKCS7_new()){ recips = sk_X509_new_null(); PKCS7_set_type(p7,NID_pkcs7_enveloped); bio_buffer = BIO_new(BIO_s_mem()); Initialized = false; } ~CryptographicMessageSyntaxInternals() { EVP_PKEY_free(pkey); PKCS7_free(p7); p7 = NULL; BIO_free_all(bio_buffer); } unsigned int GetNumberOfRecipients() const { //::STACK_OF(X509) *recips = recips; if(!recips) { return 0; } return ::sk_X509_num(recips); } STACK_OF(X509)* GetRecipients( ) const { return recips; } ::X509* GetRecipient( unsigned int i ) const { //::STACK_OF(X509) *recips = Internals->recips; ::X509 *ret = sk_X509_value(recips, i); return ret; } void SetPrivateKey(::EVP_PKEY* thepkey) { this->pkey = thepkey; } ::EVP_PKEY* GetPrivateKey() const { return pkey; } void SetCipherType(CryptographicMessageSyntax::CipherTypes ciphertype) { CipherType = ciphertype; } bool Initialize() { if(!cipher) { cipher = CreateCipher( GetCipherType() ); } if(!cipher) return false; // The following is inspired by PKCS7_encrypt // and openssl/crypto/pkcs7/enc.c if( !PKCS7_set_cipher(p7,cipher) ) return false; for(unsigned int i = 0; i < GetNumberOfRecipients(); i++) { ::X509* recip = GetRecipient(i); if (!PKCS7_add_recipient(p7,recip)) return false; } sk_X509_pop_free(recips, X509_free); /* Set the content of the signed to 'data' */ /* PKCS7_content_new(p7,NID_pkcs7_data); not used in envelope */ /* could be used, but not in this version :-) if (!nodetach) PKCS7_set_detached(p7,1); */ return true; } bool Encrypt(char *output, size_t &outlen, const char *array, size_t len) { if( !Initialized ) { bool b = Initialize(); if ( !b ) { gdcmErrorMacro( "Initialize" ); return false; } Initialized = true; } if( len > (size_t)std::numeric_limits::max() ) { gdcmErrorMacro( "len is too big: " << len ); return false; } BIO *data = BIO_new_mem_buf((const void*)array, (int)len); if(!data) { gdcmErrorMacro( "BIO_new_mem_buf" ); return false; } char buf[256]; BIO *p7bio; if ((p7bio=PKCS7_dataInit(p7,NULL)) == NULL) return false; for (;;) { int i = BIO_read(data,buf,sizeof(buf)); if (i <= 0) break; BIO_write(p7bio,buf,i); } // BIO_flush() returns 1 for success and 0 or -1 for failure. int bflush = BIO_flush(p7bio); if( bflush != 1 ) { gdcmErrorMacro( "BIO_flush: " << bflush ); return false; } if (!PKCS7_dataFinal(p7,p7bio)) { gdcmErrorMacro( "PKCS7_dataFinal" ); return false; } // WARNING: // BIO_reset() normally returns 1 for success and 0 or -1 for failure. File // BIOs are an exception, they return 0 for success and -1 for failure. if( BIO_reset(bio_buffer) != 1 ) { gdcmErrorMacro( "BIO_reset" ); return false; } i2d_PKCS7_bio(bio_buffer,p7); // (void)BIO_flush(wbio); char *binary; long biolen = BIO_get_mem_data(bio_buffer,&binary); gdcmAssertMacro( biolen >= 0 ); if ( outlen < (size_t)biolen ) { gdcmErrorMacro( "Allocation issue: " << outlen << " vs " << biolen << " from " << len ); return false; } outlen = biolen; memcpy( output, binary, outlen ); BIO_free(data); BIO_free_all(p7bio); return true; } CryptographicMessageSyntax::CipherTypes GetCipherType() const { return CipherType; } ::PKCS7 *GetP7() const { return p7; } private: STACK_OF(X509) *recips; ::EVP_PKEY *pkey; CryptographicMessageSyntax::CipherTypes CipherType; const EVP_CIPHER *cipher; ::PKCS7 *p7; BIO *bio_buffer; bool Initialized; #endif }; OpenSSLP7CryptographicMessageSyntax::OpenSSLP7CryptographicMessageSyntax() { Internals = new CryptographicMessageSyntaxInternals; } OpenSSLP7CryptographicMessageSyntax::~OpenSSLP7CryptographicMessageSyntax() { delete Internals; } void OpenSSLP7CryptographicMessageSyntax::SetCipherType( CryptographicMessageSyntax::CipherTypes type ) { #ifdef GDCM_USE_SYSTEM_OPENSSL Internals->SetCipherType( type ); #else (void)type; #endif } CryptographicMessageSyntax::CipherTypes OpenSSLP7CryptographicMessageSyntax::GetCipherType() const { #ifdef GDCM_USE_SYSTEM_OPENSSL return Internals->GetCipherType(); #else return AES256_CIPHER; // why not :) #endif } bool OpenSSLP7CryptographicMessageSyntax::Encrypt(char *output, size_t &outlen, const char *array, size_t len) const { #ifdef GDCM_USE_SYSTEM_OPENSSL // RAND_status() and RAND_event() return 1 if the PRNG has been seeded with // enough data, 0 otherwise. if( !RAND_status() ) { gdcmErrorMacro( "PRNG was not seeded properly" ); outlen = 0; return false; } return Internals->Encrypt(output, outlen, array, len); #else (void)output; (void)array; (void)len; outlen = 0; gdcmDebugMacro( "GDCM_USE_SYSTEM_OPENSSL is OFF" ); return false; #endif /* GDCM_USE_SYSTEM_OPENSSL */ } /* $ openssl smime -decrypt -in /tmp/debug.der -inform DER -recip /tmp/server.pem -inkey CA_key.pem */ bool OpenSSLP7CryptographicMessageSyntax::Decrypt(char *output, size_t &outlen, const char *array, size_t len) const { #ifdef GDCM_USE_SYSTEM_OPENSSL CryptographicMessageSyntaxInternals *x509 = Internals; ::PKCS7 *p7; #undef PKCS7_SIGNER_INFO ::PKCS7_SIGNER_INFO *si; X509_STORE_CTX *cert_ctx=NULL; X509_STORE *cert_store=NULL; BIO *data,*detached=NULL,*p7bio=NULL; char buf[1024*4]; unsigned char *pp; int i; STACK_OF(PKCS7_SIGNER_INFO) *sk; char * ptr = output; outlen = 0; OpenSSL_add_all_algorithms(); //bio_err=BIO_new_fp(stderr,BIO_NOCLOSE); //data=BIO_new(BIO_s_file()); pp=NULL; EVP_PKEY *pkey = x509->GetPrivateKey(); if( len > (size_t)std::numeric_limits::max() ) { gdcmErrorMacro( "len is too big: " << len ); return false; } data = BIO_new_mem_buf((const void*)array, (int)len); if(!data) goto err; if (pp == NULL) BIO_set_fp(data,stdin,BIO_NOCLOSE); /* Load the PKCS7 object from a file */ //if ((p7=PEM_read_bio_PKCS7(data,NULL,NULL,NULL)) == NULL) goto err; if ((p7=d2i_PKCS7_bio(data,NULL)) == NULL) goto err; if(!PKCS7_type_is_enveloped(p7)) { goto err; } // if(cert && !X509_check_private_key(cert, pkey)) { // PKCS7err(PKCS7_F_PKCS7_DECRYPT, // PKCS7_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE); // return 0; // } cert_ctx=X509_STORE_CTX_new(); /* This stuff is being setup for certificate verification. * When using SSL, it could be replaced with a * cert_stre=SSL_CTX_get_cert_store(ssl_ctx); */ cert_store=X509_STORE_new(); X509_STORE_set_default_paths(cert_store); X509_STORE_load_locations(cert_store,NULL,"../../certs"); //X509_STORE_set_verify_cb_func(cert_store,verify_callback); ERR_clear_error(); /* We need to process the data */ /* We cannot support detached encryption */ p7bio=PKCS7_dataDecode(p7,pkey,detached,NULL); if (p7bio == NULL) { printf("problems decoding\n"); goto err; } /* We now have to 'read' from p7bio to calculate digests etc. */ for (;;) { i=BIO_read(p7bio,buf,sizeof(buf)); /* print it? */ if (i <= 0) break; //fwrite(buf,1, i, stdout); memcpy(ptr, buf, i); ptr += i; outlen += i; } /* We can now verify signatures */ sk=PKCS7_get_signer_info(p7); if (sk == NULL) { //fprintf(stderr, "there are no signatures on this data\n"); } else { /* Ok, first we need to, for each subject entry, * see if we can verify */ ERR_clear_error(); for (i=0; iSetPrivateKey( pkey ); return true; #else (void)keyfile; gdcmDebugMacro( "GDCM_USE_SYSTEM_OPENSSL is OFF" ); return false; #endif } bool OpenSSLP7CryptographicMessageSyntax::ParseCertificateFile( const char *keyfile) { #ifdef GDCM_USE_SYSTEM_OPENSSL STACK_OF(X509) *recips = Internals->GetRecipients(); assert( recips ); ::X509 *x509 = NULL; ::BIO *in; if (!(in=::BIO_new_file(keyfile,"r"))) { return false; } // -> LEAK reported by valgrind... if (!(x509=::PEM_read_bio_X509(in,NULL,NULL,NULL))) { return false; } ::BIO_free(in); in = NULL; ::sk_X509_push(recips, x509); return true; #else (void)keyfile; gdcmDebugMacro( "GDCM_USE_SYSTEM_OPENSSL is OFF" ); return false; #endif } } // end namespace gdcm