1 /*
2  *
3  *  Copyright (C) 1998-2020, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module: dcmsign
15  *
16  *  Author: Marco Eichelberg
17  *
18  *  Purpose:
19  *    classes: SiCertificateVerifier
20  *
21  */
22 
23 #include "dcmtk/config/osconfig.h"
24 
25 #ifdef WITH_OPENSSL
26 
27 #include "dcmtk/dcmsign/sicert.h"
28 #include "dcmtk/dcmsign/sicertvf.h"
29 
30 BEGIN_EXTERN_C
31 #include <openssl/pem.h>
32 #include <openssl/x509.h>
33 END_EXTERN_C
34 
35 
SiCertificateVerifier()36 SiCertificateVerifier::SiCertificateVerifier()
37 : x509store(NULL)
38 , x509untrusted(NULL)
39 , enableCRLverification(OFFalse)
40 , errorCode(0)
41 {
42   x509store = X509_STORE_new();
43   x509untrusted = sk_X509_new_null();
44 }
45 
46 
~SiCertificateVerifier()47 SiCertificateVerifier::~SiCertificateVerifier()
48 {
49   X509_STORE_free(x509store);
50   sk_X509_pop_free(x509untrusted, X509_free);
51 }
52 
53 
getTrustedCertStore()54 X509_STORE *SiCertificateVerifier::getTrustedCertStore()
55 {
56   return x509store;
57 }
58 
59 
getUntrustedCerts()60 stack_st_X509 *SiCertificateVerifier::getUntrustedCerts()
61 {
62   return x509untrusted;
63 }
64 
65 
addTrustedCertificateFile(const char * fileName,int fileType)66 OFCondition SiCertificateVerifier::addTrustedCertificateFile(const char *fileName, int fileType)
67 {
68   /* fileType should be X509_FILETYPE_PEM or X509_FILETYPE_ASN1 */
69   X509_LOOKUP *x509_lookup = X509_STORE_add_lookup(x509store, X509_LOOKUP_file());
70   if (x509_lookup == NULL) return SI_EC_OpenSSLFailure;
71   if (! X509_LOOKUP_load_file(x509_lookup, fileName, fileType)) return SI_EC_CannotRead;
72   return EC_Normal;
73 }
74 
75 
addUntrustedCertificateFile(const char * fileName,int fileType)76 OFCondition SiCertificateVerifier::addUntrustedCertificateFile(const char *fileName, int fileType)
77 {
78   OFCondition result = EC_Normal;
79   if (x509untrusted)
80   {
81     // PEM has different loading code because a PEM file can contain several certificates and CRLs
82     if (fileType == X509_FILETYPE_PEM)
83     {
84       BIO *bio = BIO_new_file(fileName, "r");
85       if (bio)
86       {
87         STACK_OF(X509_INFO) *x509infostack = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
88         if (x509infostack)
89         {
90           for (int i = 0; i < sk_X509_INFO_num(x509infostack); i++)
91           {
92             X509_INFO *xi = sk_X509_INFO_value(x509infostack, i);
93             if (xi->x509)
94             {
95               // move certificate to our list of untrusted certificates
96               sk_X509_push(x509untrusted, xi->x509);
97               xi->x509 = NULL;
98             }
99           }
100           // delete the remaining x509infostack
101           sk_X509_INFO_pop_free(x509infostack, X509_INFO_free);
102         } else result = SI_EC_CannotRead;
103         BIO_free(bio);
104       } else result = SI_EC_CannotRead;
105     }
106     else if (fileType == X509_FILETYPE_ASN1)
107     {
108       // load a single certificate in ASN.1 DER format
109       BIO *bio = BIO_new_file(fileName, "rb");
110       if (bio)
111       {
112         X509 *xcert = d2i_X509_bio(bio, NULL);
113         if (xcert)
114         {
115           sk_X509_push(x509untrusted, xcert);
116         } else result = SI_EC_CannotRead;
117         BIO_free(bio);
118       } else result = SI_EC_CannotRead;
119     } else result = SI_EC_InvalidFiletype;
120   } else result = SI_EC_CannotRead;
121 
122   return result;
123 }
124 
125 
addTrustedCertificateDir(const char * pathName,int fileType)126 OFCondition SiCertificateVerifier::addTrustedCertificateDir(const char *pathName, int fileType)
127 {
128   /* fileType should be X509_FILETYPE_PEM or X509_FILETYPE_ASN1 */
129   X509_LOOKUP *x509_lookup = X509_STORE_add_lookup(x509store, X509_LOOKUP_hash_dir());
130   if (x509_lookup == NULL) return SI_EC_OpenSSLFailure;
131   if (! X509_LOOKUP_add_dir(x509_lookup, pathName, fileType)) return SI_EC_CannotRead;
132   return EC_Normal;
133 }
134 
135 
addCertificateRevocationList(const char * fileName,int fileType)136 OFCondition SiCertificateVerifier::addCertificateRevocationList(const char *fileName, int fileType)
137 {
138   OFCondition result = SI_EC_CannotRead;
139   X509_CRL *x509crl = NULL;
140   if (fileName)
141   {
142     BIO *in = BIO_new(BIO_s_file());
143     if (in)
144     {
145       if (BIO_read_filename(in, fileName) > 0)
146       {
147         if (fileType == X509_FILETYPE_ASN1)
148         {
149           x509crl = d2i_X509_CRL_bio(in, NULL);
150         } else {
151           x509crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL);
152         }
153         if (x509crl)
154         {
155           X509_STORE_add_crl(x509store, x509crl); // creates a copy of the CRL object
156           X509_CRL_free(x509crl);
157           enableCRLverification = OFTrue;
158           result = EC_Normal;
159         }
160       }
161       BIO_free(in);
162     }
163   }
164   return result;
165 }
166 
167 
setCRLverification(OFBool enabled)168 void SiCertificateVerifier::setCRLverification(OFBool enabled)
169 {
170   enableCRLverification = enabled;
171 }
172 
173 
174 extern "C"
175 {
176   int sicertvf_verify_callback(int deflt, X509_STORE_CTX *ctx);
177 }
178 
sicertvf_verify_callback(int deflt,X509_STORE_CTX * ctx)179 int sicertvf_verify_callback(int deflt, X509_STORE_CTX *ctx)
180 {
181   if (ctx)
182   {
183     void *p = X509_STORE_CTX_get_ex_data(ctx, 0);
184     if (p)
185     {
186       SiCertificateVerifier *pthis = OFreinterpret_cast(SiCertificateVerifier *, p);
187       return pthis->verifyCallback(deflt, ctx);
188     }
189   }
190   return deflt;
191 }
192 
verifyCertificate(SiCertificate & certificate)193 OFCondition SiCertificateVerifier::verifyCertificate(SiCertificate& certificate)
194 {
195   errorCode = 0;
196   X509 *rawcert = certificate.getRawCertificate();
197   if (rawcert == NULL) return SI_EC_VerificationFailed_NoCertificate;
198 
199   X509_STORE_CTX *ctx = X509_STORE_CTX_new();
200   X509_STORE_CTX_init(ctx, x509store, rawcert, x509untrusted);
201 
202   X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
203   if (param)
204   {
205     if (enableCRLverification)
206     {
207       // enable CRL checking for the signer certificate
208       X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
209     }
210     X509_VERIFY_PARAM_set_depth(param, 100); // that's the OpenSSL default
211     X509_STORE_CTX_set0_param(ctx, param);
212   }
213 
214   X509_STORE_CTX_set_ex_data(ctx, 0, OFreinterpret_cast(void *, this));
215   X509_STORE_CTX_set_verify_cb(ctx, sicertvf_verify_callback);
216 
217   // If a complete chain can be built and validated X509_verify_cert() returns 1,
218   // otherwise it returns zero, in exceptional circumstances it can also return a negative code.
219   int ok = X509_verify_cert(ctx);
220 
221   errorCode = X509_STORE_CTX_get_error(ctx);
222   X509_STORE_CTX_cleanup(ctx);
223   X509_STORE_CTX_free(ctx);
224 
225   if (ok == 1) return EC_Normal; else return SI_EC_VerificationFailed_NoTrust;
226 }
227 
verifyCallback(int deflt,X509_STORE_CTX * ctx)228 int SiCertificateVerifier::verifyCallback(int deflt, X509_STORE_CTX *ctx)
229 {
230   if (X509_STORE_CTX_get_error(ctx) == X509_V_ERR_CERT_REVOKED)
231   {
232      // The signer certificate is on the revocation list. By default, this means that
233      // the certificate verification will fail, independent from the timestamp of the signature
234      // and the timestamp of the revocation. At this point we could add additional code that
235      // compares the time of revocation with the DICOM signature datetime or (preferrably) a
236      // certified timestamp that might also be present. If the revocation occured after the time of
237      // signature, we could still accept the certificate and thus the signature.
238      // For now, we retain the OpenSSL default behaviour.
239   }
240   return deflt;
241 }
242 
lastErrorIsCertExpiry() const243 OFBool SiCertificateVerifier::lastErrorIsCertExpiry() const
244 {
245   return (errorCode == X509_V_ERR_CERT_HAS_EXPIRED);
246 }
247 
lastError() const248 const char *SiCertificateVerifier::lastError() const
249 {
250   return X509_verify_cert_error_string(errorCode);
251 }
252 
253 
254 #else /* WITH_OPENSSL */
255 
256 int sicertvf_cc_dummy_to_keep_linker_from_moaning = 0;
257 
258 #endif
259