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