Skip to content

Instantly share code, notes, and snippets.

@dnadlinger
Last active January 4, 2016 00:49
Show Gist options
  • Select an option

  • Save dnadlinger/8544161 to your computer and use it in GitHub Desktop.

Select an option

Save dnadlinger/8544161 to your computer and use it in GitHub Desktop.
/**
* 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.");
}
}
/**
* 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