From cc5485f8934b5a6024a80ac2cea147aa7816c0c8 Mon Sep 17 00:00:00 2001
From: Yedidya Feldblum <yfeldblum@fb.com>
Date: Thu, 16 Jun 2016 18:30:25 -0700
Subject: [PATCH] Wrappers for some of OpenSSL's crypto hash functions

Summary:
[Folly] Wrappers for some of OpenSSL's crypto hash functions.

Wraps some of the OpenSSL crypto hash functions with variants that take `ByteRange` for input and `MutableByteRange` for output, and also variants that take `const IOBuf&` for input as well.

These are a bit nicer to use than passing pointers and lengths separately.

Reviewed By: ivmaykov

Differential Revision: D3434562

fbshipit-source-id: 3688ef11680a029b7664ac417a7781e70f9c6926
---
 folly/Makefile.am                  |   2 +
 folly/ssl/OpenSSLHash.cpp          |  34 ++++++
 folly/ssl/OpenSSLHash.h            | 189 +++++++++++++++++++++++++++++
 folly/ssl/test/OpenSSLHashTest.cpp |  69 +++++++++++
 folly/test/Makefile.am             |   5 +
 5 files changed, 299 insertions(+)
 create mode 100644 folly/ssl/OpenSSLHash.cpp
 create mode 100644 folly/ssl/OpenSSLHash.h
 create mode 100644 folly/ssl/test/OpenSSLHashTest.cpp

diff --git a/folly/Makefile.am b/folly/Makefile.am
index bfd670cd..08ce2f23 100644
--- a/folly/Makefile.am
+++ b/folly/Makefile.am
@@ -294,6 +294,7 @@ nobase_follyinclude_HEADERS = \
 	SpinLock.h \
 	SpookyHashV1.h \
 	SpookyHashV2.h \
+	ssl/OpenSSLHash.h \
 	stats/BucketedTimeSeries-defs.h \
 	stats/BucketedTimeSeries.h \
 	stats/Histogram-defs.h \
@@ -432,6 +433,7 @@ libfolly_la_SOURCES = \
 	SocketAddress.cpp \
 	SpookyHashV1.cpp \
 	SpookyHashV2.cpp \
+	ssl/OpenSSLHash.cpp \
 	stats/Instantiations.cpp \
 	Subprocess.cpp \
 	ThreadCachedArena.cpp \
diff --git a/folly/ssl/OpenSSLHash.cpp b/folly/ssl/OpenSSLHash.cpp
new file mode 100644
index 00000000..9e3581aa
--- /dev/null
+++ b/folly/ssl/OpenSSLHash.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * 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.
+ */
+
+#include <folly/ssl/OpenSSLHash.h>
+
+#include <folly/Format.h>
+
+namespace folly {
+namespace ssl {
+
+void OpenSSLHash::check_out_size_throw(size_t size, MutableByteRange out) {
+  throw std::invalid_argument(folly::sformat(
+      "expected out of size {} but was of size {}", size, out.size()));
+}
+
+void OpenSSLHash::check_libssl_result_throw() {
+  throw std::runtime_error("openssl crypto function failed");
+}
+
+}
+}
diff --git a/folly/ssl/OpenSSLHash.h b/folly/ssl/OpenSSLHash.h
new file mode 100644
index 00000000..e3ea65bc
--- /dev/null
+++ b/folly/ssl/OpenSSLHash.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include <folly/Range.h>
+#include <folly/io/IOBuf.h>
+
+namespace folly {
+namespace ssl {
+
+/// Warning:
+/// These functions are not thread-safe unless you initialize OpenSSL.
+class OpenSSLHash {
+ public:
+
+  class Digest {
+   public:
+    Digest() {
+      EVP_MD_CTX_init(&ctx_);
+    }
+    ~Digest() {
+      EVP_MD_CTX_cleanup(&ctx_);
+    }
+    void hash_init(const EVP_MD* md) {
+      md_ = md;
+      check_libssl_result(1, EVP_DigestInit_ex(&ctx_, md, nullptr));
+    }
+    void hash_update(ByteRange data) {
+      check_libssl_result(1, EVP_DigestUpdate(&ctx_, data.data(), data.size()));
+    }
+    void hash_update(const IOBuf& data) {
+      for (auto r : data) {
+        hash_update(r);
+      }
+    }
+    void hash_final(MutableByteRange out) {
+      const auto size = EVP_MD_size(md_);
+      check_out_size(size, out);
+      unsigned int len = 0;
+      check_libssl_result(1, EVP_DigestFinal_ex(&ctx_, out.data(), &len));
+      check_libssl_result(size, len);
+      md_ = nullptr;
+    }
+   private:
+    const EVP_MD* md_ = nullptr;
+    EVP_MD_CTX ctx_;
+  };
+
+  static void hash(
+      MutableByteRange out,
+      const EVP_MD* md,
+      ByteRange data) {
+    Digest hash;
+    hash.hash_init(md);
+    hash.hash_update(data);
+    hash.hash_final(out);
+  }
+  static void hash(
+      MutableByteRange out,
+      const EVP_MD* md,
+      const IOBuf& data) {
+    Digest hash;
+    hash.hash_init(md);
+    hash.hash_update(data);
+    hash.hash_final(out);
+  }
+  static void sha1(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_sha1(), data);
+  }
+  static void sha1(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_sha1(), data);
+  }
+  static void sha256(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_sha256(), data);
+  }
+  static void sha256(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_sha256(), data);
+  }
+
+  class Hmac {
+   public:
+    Hmac() {
+      HMAC_CTX_init(&ctx_);
+    }
+    ~Hmac() {
+      HMAC_CTX_cleanup(&ctx_);
+    }
+    void hash_init(const EVP_MD* md, ByteRange key) {
+      md_ = md;
+      check_libssl_result(
+          1, HMAC_Init_ex(&ctx_, key.data(), key.size(), md_, nullptr));
+    }
+    void hash_update(ByteRange data) {
+      check_libssl_result(1, HMAC_Update(&ctx_, data.data(), data.size()));
+    }
+    void hash_update(const IOBuf& data) {
+      for (auto r : data) {
+        hash_update(r);
+      }
+    }
+    void hash_final(MutableByteRange out) {
+      const auto size = EVP_MD_size(md_);
+      check_out_size(size, out);
+      unsigned int len = 0;
+      check_libssl_result(1, HMAC_Final(&ctx_, out.data(), &len));
+      check_libssl_result(size, len);
+      md_ = nullptr;
+    }
+   private:
+    const EVP_MD* md_ = nullptr;
+    HMAC_CTX ctx_;
+  };
+
+  static void hmac(
+      MutableByteRange out,
+      const EVP_MD* md,
+      ByteRange key,
+      ByteRange data) {
+    Hmac hmac;
+    hmac.hash_init(md, key);
+    hmac.hash_update(data);
+    hmac.hash_final(out);
+  };
+  static void hmac(
+      MutableByteRange out,
+      const EVP_MD* md,
+      ByteRange key,
+      const IOBuf& data) {
+    Hmac hmac;
+    hmac.hash_init(md, key);
+    hmac.hash_update(data);
+    hmac.hash_final(out);
+  }
+  static void hmac_sha1(
+      MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_sha1(), key, data);
+  }
+  static void hmac_sha1(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_sha1(), key, data);
+  }
+  static void hmac_sha256(
+      MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_sha256(), key, data);
+  }
+  static void hmac_sha256(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_sha256(), key, data);
+  }
+
+ private:
+  static inline void check_out_size(size_t size, MutableByteRange out) {
+    if (LIKELY(size == out.size())) {
+      return;
+    }
+    check_out_size_throw(size, out);
+  }
+  static void check_out_size_throw(size_t size, MutableByteRange out);
+
+  static inline void check_libssl_result(int expected, int result) {
+    if (LIKELY(result == expected)) {
+      return;
+    }
+    check_libssl_result_throw();
+  }
+  static void check_libssl_result_throw();
+
+};
+
+}
+}
diff --git a/folly/ssl/test/OpenSSLHashTest.cpp b/folly/ssl/test/OpenSSLHashTest.cpp
new file mode 100644
index 00000000..129cf406
--- /dev/null
+++ b/folly/ssl/test/OpenSSLHashTest.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * 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.
+ */
+
+#include <folly/ssl/OpenSSLHash.h>
+
+#include <gtest/gtest.h>
+
+#include <folly/io/IOBufQueue.h>
+
+using namespace std;
+using namespace folly;
+using namespace folly::ssl;
+
+namespace {
+
+class OpenSSLHashTest : public testing::Test {};
+
+}
+
+TEST_F(OpenSSLHashTest, sha256) {
+  IOBuf buf;
+  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("foo"))));
+  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("bar"))));
+  EXPECT_EQ(3, buf.countChainElements());
+  EXPECT_EQ(6, buf.computeChainDataLength());
+
+  auto expected = vector<uint8_t>(32);
+  auto combined = ByteRange(StringPiece("foobar"));
+  SHA256(combined.data(), combined.size(), expected.data());
+
+  auto out = vector<uint8_t>(32);
+  OpenSSLHash::sha256(range(out), buf);
+  EXPECT_EQ(expected, out);
+}
+
+TEST_F(OpenSSLHashTest, hmac_sha256) {
+  auto key = ByteRange(StringPiece("qwerty"));
+
+  IOBuf buf;
+  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("foo"))));
+  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("bar"))));
+  EXPECT_EQ(3, buf.countChainElements());
+  EXPECT_EQ(6, buf.computeChainDataLength());
+
+  auto expected = vector<uint8_t>(32);
+  auto combined = ByteRange(StringPiece("foobar"));
+  HMAC(
+      EVP_sha256(),
+      key.data(), key.size(),
+      combined.data(), combined.size(),
+      expected.data(), nullptr);
+
+  auto out = vector<uint8_t>(32);
+  OpenSSLHash::hmac_sha256(range(out), key, buf);
+  EXPECT_EQ(expected, out);
+}
diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am
index 987db2ee..7a39a820 100644
--- a/folly/test/Makefile.am
+++ b/folly/test/Makefile.am
@@ -266,4 +266,9 @@ function_test_SOURCES = FunctionTest.cpp
 function_test_LDADD = libfollytestmain.la
 TESTS += function_test
 
+ssl_test_SOURCES = \
+		../ssl/OpenSSLHashTest.cpp
+ssl_test_LDADD = libfollytestmain.la
+TESTS += ssl_test
+
 check_PROGRAMS += $(TESTS)
-- 
2.34.1