From cc5485f8934b5a6024a80ac2cea147aa7816c0c8 Mon Sep 17 00:00:00 2001 From: Yedidya Feldblum 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 + +#include + +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 +#include +#include + +#include +#include + +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 + +#include + +#include + +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(32); + auto combined = ByteRange(StringPiece("foobar")); + SHA256(combined.data(), combined.size(), expected.data()); + + auto out = vector(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(32); + auto combined = ByteRange(StringPiece("foobar")); + HMAC( + EVP_sha256(), + key.data(), key.size(), + combined.data(), combined.size(), + expected.data(), nullptr); + + auto out = vector(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