1 /// 2 module imap.ssl; 3 import imap.sil : SILdoc; 4 import std.stdio; 5 import std.string; 6 import deimos.openssl.ssl; 7 import deimos.openssl.err; 8 import deimos.openssl.x509; 9 import deimos.openssl.x509_vfy; // needed to use openssl master 10 import deimos.openssl.pem; 11 import deimos.openssl.evp; 12 13 import imap.defines; 14 import imap.socket; 15 import imap.session; 16 17 /// 18 enum STDIN_FILENO = 0; 19 20 version(Posix) import core.sys.posix.unistd : isatty; 21 else 22 { 23 // FIXME 24 bool isatty(int fileno) 25 { 26 return false; 27 } 28 } 29 30 31 /// 32 X509* getPeerCertificate(ref SSL context) 33 { 34 import std.exception : enforce; 35 X509* cert= SSL_get_peer_certificate(&context); 36 enforce(cert, "unable to get peer certificate"); 37 return cert; 38 } 39 40 @SILdoc("Get SSL/TLS certificate check it, maybe ask user about it and act accordingly.") 41 Status getCert(ref Session session) 42 { 43 import std.exception : enforce; 44 X509* pcert = getPeerCertificate(*session.sslConnection); 45 enforce(pcert !is null); 46 auto cert = pcert; 47 48 scope(exit) 49 X509_free(pcert); 50 51 long verify = SSL_get_verify_result(session.sslConnection); 52 enforce(((verify == X509_V_OK) || 53 (verify == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || 54 (verify == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)), 55 format!"certificate verification failed; %d\n"(verify)); 56 57 if (verify != X509_V_OK) { 58 auto md = cert.getDigest(EVP_md5()); 59 enforce(checkCert(cert,md) == Status.success,"certificate mismatch occurred"); 60 enforce(isatty(STDIN_FILENO) != 0,"certificate error: cannot accept certificate in non-interactivemode"); 61 printCert(cert, md); 62 storeCert(cert); 63 } 64 return Status.success; 65 } 66 67 /// 68 enum Status 69 { 70 success, 71 failure, 72 } 73 74 @SILdoc("Check if the SSL/TLS certificate exists in the certificates file.") 75 Status checkCert(X509* pcert, string pmd) 76 { 77 import std.file : exists; 78 Status status = Status.failure; 79 X509* certp; 80 81 string certf = getFilePath("certificates"); 82 if (!certf.exists()) 83 return Status.failure; 84 85 auto file = File(certf,"r"); 86 string pcertSubject = pcert.getSubject(); 87 string pIssuerName = pcert.getIssuerName(); 88 string pSerial = pcert.getSerial(); 89 90 while ((certp = readX509(file,certp)) !is null) 91 { 92 auto cert = certp; 93 if (cert.getSubject != pcertSubject || (cert.getIssuerName() != pIssuerName) || 94 (cert.getSerial() != pSerial)) 95 continue; 96 97 auto digest = getDigest(cert, EVP_md5()); 98 if (digest.length != pmd.length) 99 continue; 100 101 if (digest != pmd) 102 { 103 status = Status.failure; 104 break; 105 } 106 status = Status.success; 107 break; 108 } 109 110 X509_free(certp); 111 112 return status; 113 } 114 115 X509* readX509(File file, ref X509* cert) 116 { 117 return PEM_read_X509(file.getFP(), &cert, null, null); 118 } 119 120 121 122 string getDigest(X509* cert,const(EVP_MD)* type) 123 { 124 import std.exception : enforce; 125 import std.format : format; 126 ubyte[EVP_MAX_MD_SIZE] md; 127 uint len; 128 auto result = X509_digest(cert,type,md.ptr,&len); 129 enforce(result == 1, "failed to get digest for certificate"); 130 enforce(len > 0, format!"X509_digest returned digest of length: %s"(len)); 131 return cast(string)(md[0 .. len].idup); 132 } 133 134 135 136 /// 137 string getIssuerName(X509* cert) 138 { 139 import core.memory : pureFree; 140 import std.string : fromStringz; 141 char* s = X509_NAME_oneline(X509_get_issuer_name(cert), null, 0); 142 scope(exit) pureFree(s); 143 return s.fromStringz.idup; 144 } 145 146 /// 147 string getSubject(X509* cert) 148 { 149 import core.memory : pureFree; 150 import std.string : fromStringz; 151 char* s = X509_NAME_oneline(X509_get_subject_name(cert), null, 0); 152 scope(exit) pureFree(s); 153 return s.fromStringz.idup; 154 } 155 156 /// 157 string asHex(string s) 158 { 159 import std.algorithm : map; 160 import std.format : format; 161 import std.array : array; 162 import std.string : join; 163 return s.map!(c => format!"%02X"(c)).array.join.idup; 164 } 165 166 167 @SILdoc("Print information about the SSL/TLS certificate.") 168 void printCert(X509* cert, string fingerprint) 169 { 170 writefln("Server certificate subject: %s",cert.getSubject); 171 writefln("Server certificate issuer: %s",getIssuerName(cert)); 172 writefln("Server certificate serial: %s", cert.getSerial); 173 writefln("Server key fingerprint: %s",fingerprint.asHex); 174 } 175 176 177 @SILdoc("Extract certificate serial number as a string.") 178 string getSerial(X509* cert) 179 { 180 import std.string : fromStringz; 181 import std.format : format; 182 string buf; 183 184 ASN1_INTEGER* serial = X509_get_serialNumber(cert); 185 if (serial.length <= cast(int)long.sizeof) { 186 long num = ASN1_INTEGER_get(serial); 187 if (serial.type == V_ASN1_NEG_INTEGER) { 188 buf~= format!"-%X"(-num); 189 } else { 190 buf ~= format!"%X"(num); 191 } 192 } else { 193 if (serial.type == V_ASN1_NEG_INTEGER) { 194 buf ~= "-"; 195 } 196 foreach(i; 0 .. serial.length) 197 { 198 buf ~= format!"%02X"(serial.data[i]); 199 } 200 } 201 return buf; 202 } 203 204 205 @SILdoc("Store the SSL/TLS certificate after asking the user to accept/reject it.") 206 void storeCert(X509* cert) 207 { 208 import std.string : toLower; 209 import std.stdio : stdin, writef, File; 210 import core.memory : pureFree; 211 import std.range : front; 212 import std.conv : to; 213 import std.exception : enforce; 214 215 char c; 216 do { 217 writef("(R)eject, accept (t)emporarily or accept (p)ermanently? "); 218 do {} while (stdin.eof); 219 c = stdin.rawRead(new char[1]).toLower.front.to!char; 220 } while (c != 'r' && c != 't' && c != 'p'); 221 222 enforce(c != 'r', "certificate rejected"); 223 if (c == 't') 224 return; 225 auto certf = getFilePath("certificates"); 226 auto file = File(certf,"a"); 227 char* s = X509_NAME_oneline(X509_get_subject_name(cert), null, 0); 228 file.writefln("Subject: %s", s); 229 pureFree(s); 230 s = X509_NAME_oneline(X509_get_issuer_name(cert), null, 0); 231 file.writefln("Issuer: %s", s); 232 pureFree(s); 233 auto serialNo = getSerial(cert); 234 file.writefln("Serial: %s", serialNo); 235 236 PEM_write_X509(file.getFP(), cert); 237 238 file.writefln(""); 239 } 240 241 /// 242 string getFilePath(string subDir) 243 { 244 import std.path : expandTilde, dirSeparator; 245 return expandTilde("~" ~ dirSeparator ~ subDir); 246 } 247