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