Last active
January 4, 2016 00:49
-
-
Save dnadlinger/8544161 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Checks if the peer is authorized after the SSL handshake has been | |
| * completed on the given connection and throws an TSSLException if not. | |
| * | |
| * Params: | |
| * ssl = The SSL connection to check. | |
| * accessManager = The access manager to check the peer against. | |
| * peerAddress = The (IP) address of the peer. | |
| * hostName = The host name of the peer. | |
| */ | |
| void authorize(SSL* ssl, TAccessManager accessManager, | |
| Address peerAddress, lazy string hostName | |
| ) { | |
| alias TAccessManager.Decision Decision; | |
| auto rc = SSL_get_verify_result(ssl); | |
| if (rc != X509_V_OK) { | |
| throw new TSSLException("SSL_get_verify_result(): " ~ | |
| to!string(X509_verify_cert_error_string(rc))); | |
| } | |
| auto cert = SSL_get_peer_certificate(ssl); | |
| if (cert is null) { | |
| // Certificate is not present. | |
| if (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) { | |
| throw new TSSLException("Peer did not present certificate."); | |
| } | |
| // If we don't have an access manager set, we don't intend to verify the | |
| // client identity, so everything's fine. | |
| if (accessManager) { | |
| throw new TSSLException( | |
| "Access manager set, but peer did not present certificate."); | |
| } | |
| return; | |
| } | |
| scope(exit) X509_free(cert); | |
| if (accessManager is null) { | |
| // No access manager set, can return immediately as the cert is valid | |
| // and all peers are authorized. | |
| return; | |
| } | |
| // Both certificate and access manager are present. | |
| auto decision = accessManager.verify(peerAddress); | |
| if (decision != Decision.SKIP) { | |
| if (decision != Decision.ALLOW) { | |
| throw new TSSLException("Access denied based on peer IP address."); | |
| } | |
| return; | |
| } | |
| // Check subjectAltName(s), if present. | |
| auto alternatives = cast(STACK_OF!(GENERAL_NAME)*) | |
| X509_get_ext_d2i(cert, NID_subject_alt_name, null, null); | |
| if (alternatives !is null) { | |
| loop: foreach (int i; 0 .. sk_GENERAL_NAME_num(alternatives)) { | |
| auto name = sk_GENERAL_NAME_value(alternatives, i); | |
| if (name is null) { | |
| continue; | |
| } | |
| immutable length = ASN1_STRING_length(name.d.ia5); | |
| auto data = ASN1_STRING_data(name.d.ia5)[0 .. length]; | |
| switch (name.type) { | |
| case GENERAL_NAME.GEN_DNS: | |
| decision = accessManager.verify(hostName, cast(char[])data); | |
| break loop; | |
| case GENERAL_NAME.GEN_IPADD: | |
| decision = accessManager.verify(peerAddress, data); | |
| break loop; | |
| default: | |
| // Do nothing. | |
| } | |
| } | |
| // DMD @@BUG@@: Empty template arguments parens should not be needed. | |
| sk_GENERAL_NAME_pop_free!()(alternatives, &GENERAL_NAME_free); | |
| } | |
| // If we are alredy done, return. | |
| if (decision != Decision.SKIP) { | |
| if (decision != Decision.ALLOW) { | |
| throw new TSSLException("Access denied based on peer subjectAltName."); | |
| } | |
| return; | |
| } | |
| // Check commonName. | |
| auto name = X509_get_subject_name(cert); | |
| if (name !is null) { | |
| int lastId = -1; | |
| while (decision == Decision.SKIP) { | |
| lastId = X509_NAME_get_index_by_NID(name, NID_commonName, lastId); | |
| if (lastId == -1) | |
| break; | |
| auto entry = X509_NAME_get_entry(name, lastId); | |
| if (entry is null) | |
| continue; | |
| auto common = X509_NAME_ENTRY_get_data(entry); | |
| char* utf8; | |
| auto size = ASN1_STRING_to_UTF8(&utf8, common); | |
| decision = accessManager.verify(hostName, utf8[0 .. size]); | |
| CRYPTO_free(utf8); | |
| } | |
| } | |
| if (decision != Decision.ALLOW) { | |
| throw new TSSLException("Authorize: Could not authorize peer."); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Default access manager implementation, which just checks the host name | |
| * resp. IP address of the connection against the certificate. | |
| */ | |
| class TDefaultClientAccessManager : TAccessManager { | |
| override Decision verify(Address address) { | |
| return Decision.SKIP; | |
| } | |
| override Decision verify(string host, const(char)[] certHost) { | |
| if (host.empty || certHost.empty) { | |
| return Decision.SKIP; | |
| } | |
| return (matchName(host, certHost) ? Decision.ALLOW : Decision.SKIP); | |
| } | |
| override Decision verify(Address address, ubyte[] certAddress) { | |
| bool match; | |
| if (certAddress.length == 4) { | |
| if (auto ia = cast(InternetAddress)address) { | |
| match = ((cast(ubyte*)ia.addr())[0 .. 4] == certAddress[]); | |
| } | |
| } else if (certAddress.length == 16) { | |
| if (auto ia = cast(Internet6Address)address) { | |
| match = (ia.addr() == certAddress[]); | |
| } | |
| } | |
| return (match ? Decision.ALLOW : Decision.SKIP); | |
| } | |
| } | |
| private { | |
| /** | |
| * Matches a name with a pattern. The pattern may include wildcard. A single | |
| * wildcard "*" can match up to one component in the domain name. | |
| * | |
| * Params: | |
| * host = Host name to match, typically the SSL remote peer. | |
| * pattern = Host name pattern, typically from the SSL certificate. | |
| * | |
| * Returns: true if host matches pattern, false otherwise. | |
| */ | |
| bool matchName(const(char)[] host, const(char)[] pattern) { | |
| while (!host.empty && !pattern.empty) { | |
| if (toUpper(pattern.front) == toUpper(host.front)) { | |
| host.popFront; | |
| pattern.popFront; | |
| } else if (pattern.front == '*') { | |
| while (!host.empty && host.front != '.') { | |
| host.popFront; | |
| } | |
| pattern.popFront; | |
| } else { | |
| break; | |
| } | |
| } | |
| return (host.empty && pattern.empty); | |
| } | |
| unittest { | |
| enforce(matchName("thrift.apache.org", "*.apache.org")); | |
| enforce(!matchName("thrift.apache.org", "apache.org")); | |
| enforce(matchName("thrift.apache.org", "thrift.*.*")); | |
| enforce(matchName("", "")); | |
| enforce(!matchName("", "*")); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment