diff --git a/hacking/core.py b/hacking/core.py
index 920e99c2..3d7c079e 100755
--- a/hacking/core.py
+++ b/hacking/core.py
@@ -104,6 +104,88 @@ def hacking_todo_format(physical_line, tokens):
         return pos, "H101: Use TODO(NAME)"
 
 
+def _check_for_apache(start, lines):
+    """Check for the Apache 2.0 license header.
+
+    We strip all the newlines and extra spaces so this license string
+    should work regardless of indentation in the file.
+    """
+    APACHE2 = """
+Licensed under the Apache License, Version 2.0 (the "License"); you may
+not use this file except in compliance with the License. You may obtain
+a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License."""
+
+    # out of all the formatting I've seen, a 12 line version seems to be the
+    # longest in the source tree. So just take the 12 lines starting with where
+    # the Apache starting words were found, strip all the '#' and collapse the
+    # spaces.
+    content = ''.join(lines[start:(start + 12)])
+    content = re.sub('\#', '', content)
+    content = re.sub('\s+', ' ', content)
+    stripped_apache2 = re.sub('\s+', ' ', APACHE2)
+
+    if stripped_apache2 in content:
+        return True
+    else:
+        print "License '%s' != '%s'" % (stripped_apache2, content)
+        return False
+
+
+def _project_is_apache():
+    """Determine if a project is Apache.
+
+    Look for a key string in a set of possible license files to figure out
+    if a project looks to be Apache. This is used as a precondition for
+    enforcing license headers.
+    """
+
+    license_files = ["LICENSE"]
+    for filename in license_files:
+        try:
+            with open(filename, "r") as file:
+                for line in file:
+                    if re.search('Apache License', line):
+                        return True
+        except IOError:
+            pass
+    return False
+
+
+@flake8ext
+def hacking_has_license(physical_line, filename, lines, line_number):
+    """Check for Apache 2.0 license.
+
+    H102
+    """
+    # don't work about init files for now
+    # TODO(sdague): enforce license in init file if it's not empty of content
+    license_found = False
+
+    # skip files that are < 10 lines, which isn't enough for a license to fit
+    # this allows us to handle empty files, as well as not fail on the Okay
+    # doctests.
+    if _project_is_apache() and not line_number > 1 and len(lines) > 10:
+        for idx, line in enumerate(lines):
+            # if it's more than 10 characters in, it's probably not in the
+            # header
+            if 0 < line.find('Licensed under the Apache License') < 10:
+                if _check_for_apache(idx, lines):
+                    license_found = True
+                else:
+                    return (idx, "H102: Apache 2.0 license header not found")
+
+        if not license_found:
+            return (0, "H102: Apache 2.0 license header not found")
+
+
 @flake8ext
 def hacking_except_format(logical_line):
     r"""Check for 'except:'.
diff --git a/setup.cfg b/setup.cfg
index 9f34b471..19226bb5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,6 +28,7 @@ setup-hooks =
 flake8.extension =
     H000 = hacking.core:ProxyChecks
     H101 = hacking.core:hacking_todo_format
+    H102 = hacking.core:hacking_has_license
     H201 = hacking.core:hacking_except_format
     H202 = hacking.core:hacking_except_format_assert
     H301 = hacking.core:hacking_import_rules
@@ -44,7 +45,6 @@ flake8.extension =
     H902 = hacking.core:hacking_not_in
 
 [egg_info]
-tag_build = 
+tag_build =
 tag_date = 0
 tag_svn_revision = 0
-