aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Alexey Surkov <surkov@google.com>2016-05-19 08:56:08 -0800
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2016-05-19 10:03:34 -0700
commitbb465cdc0ed9c3b9b4f031505ea2294375677807 (patch)
tree1d78377f70c94c7cd528df9f7ecc18ee22d86b17
parent7584aa60a4f37cf14f73cbd8caf1cd964da5a32d (diff)
Google authentication for GCS file system.
Implements an authentication mechanism based on Application Default Credentials: https://developers.google.com/identity/protocols/application-default-credentials https://developers.google.com/identity/protocols/OAuth2ServiceAccount Change: 122741738
-rw-r--r--boringssl.BUILD410
-rw-r--r--tensorflow/core/platform/cloud/BUILD114
-rw-r--r--tensorflow/core/platform/cloud/auth_provider.h52
-rw-r--r--tensorflow/core/platform/cloud/base64.cc221
-rw-r--r--tensorflow/core/platform/cloud/base64.h36
-rw-r--r--tensorflow/core/platform/cloud/base64_test.cc33
-rw-r--r--tensorflow/core/platform/cloud/gcs_file_system.cc33
-rw-r--r--tensorflow/core/platform/cloud/gcs_file_system.h8
-rw-r--r--tensorflow/core/platform/cloud/gcs_file_system_test.cc90
-rw-r--r--tensorflow/core/platform/cloud/google_auth_provider.cc190
-rw-r--r--tensorflow/core/platform/cloud/google_auth_provider.h57
-rw-r--r--tensorflow/core/platform/cloud/google_auth_provider_test.cc200
-rw-r--r--tensorflow/core/platform/cloud/http_request.cc50
-rw-r--r--tensorflow/core/platform/cloud/http_request.h19
-rw-r--r--tensorflow/core/platform/cloud/http_request_fake.h166
-rw-r--r--tensorflow/core/platform/cloud/http_request_test.cc35
-rw-r--r--tensorflow/core/platform/cloud/oauth_client.cc288
-rw-r--r--tensorflow/core/platform/cloud/oauth_client.h65
-rw-r--r--tensorflow/core/platform/cloud/oauth_client_test.cc203
-rw-r--r--tensorflow/core/platform/cloud/testdata/application_default_credentials.json6
-rw-r--r--tensorflow/core/platform/cloud/testdata/service_account_credentials.json12
-rw-r--r--tensorflow/core/platform/cloud/testdata/service_account_public_key.txt9
-rw-r--r--tensorflow/core/platform/env.h4
-rw-r--r--tensorflow/workspace.bzl7
-rw-r--r--third_party/boringssl/BUILD13
-rw-r--r--third_party/boringssl/err_data.c1236
26 files changed, 3427 insertions, 130 deletions
diff --git a/boringssl.BUILD b/boringssl.BUILD
new file mode 100644
index 0000000000..054b9ecbd9
--- /dev/null
+++ b/boringssl.BUILD
@@ -0,0 +1,410 @@
+package(default_visibility = ["//tensorflow:__subpackages__"])
+
+licenses(["restricted"]) # OpenSSL license, partly BSD-like
+
+# See https://boringssl.googlesource.com/boringssl/+/master/INCORPORATING.md
+# on how to re-generate the list of source files.
+
+crypto_headers = [
+ "include/openssl/aead.h",
+ "include/openssl/aes.h",
+ "include/openssl/arm_arch.h",
+ "include/openssl/asn1.h",
+ "include/openssl/asn1_mac.h",
+ "include/openssl/asn1t.h",
+ "include/openssl/base.h",
+ "include/openssl/base64.h",
+ "include/openssl/bio.h",
+ "include/openssl/blowfish.h",
+ "include/openssl/bn.h",
+ "include/openssl/buf.h",
+ "include/openssl/buffer.h",
+ "include/openssl/bytestring.h",
+ "include/openssl/cast.h",
+ "include/openssl/chacha.h",
+ "include/openssl/cipher.h",
+ "include/openssl/cmac.h",
+ "include/openssl/conf.h",
+ "include/openssl/cpu.h",
+ "include/openssl/crypto.h",
+ "include/openssl/curve25519.h",
+ "include/openssl/des.h",
+ "include/openssl/dh.h",
+ "include/openssl/digest.h",
+ "include/openssl/dsa.h",
+ "include/openssl/ec.h",
+ "include/openssl/ec_key.h",
+ "include/openssl/ecdh.h",
+ "include/openssl/ecdsa.h",
+ "include/openssl/engine.h",
+ "include/openssl/err.h",
+ "include/openssl/evp.h",
+ "include/openssl/ex_data.h",
+ "include/openssl/hkdf.h",
+ "include/openssl/hmac.h",
+ "include/openssl/lhash.h",
+ "include/openssl/lhash_macros.h",
+ "include/openssl/md4.h",
+ "include/openssl/md5.h",
+ "include/openssl/mem.h",
+ "include/openssl/newhope.h",
+ "include/openssl/nid.h",
+ "include/openssl/obj.h",
+ "include/openssl/obj_mac.h",
+ "include/openssl/objects.h",
+ "include/openssl/opensslconf.h",
+ "include/openssl/opensslv.h",
+ "include/openssl/ossl_typ.h",
+ "include/openssl/pem.h",
+ "include/openssl/pkcs12.h",
+ "include/openssl/pkcs7.h",
+ "include/openssl/pkcs8.h",
+ "include/openssl/poly1305.h",
+ "include/openssl/pqueue.h",
+ "include/openssl/rand.h",
+ "include/openssl/rc4.h",
+ "include/openssl/ripemd.h",
+ "include/openssl/rsa.h",
+ "include/openssl/safestack.h",
+ "include/openssl/sha.h",
+ "include/openssl/srtp.h",
+ "include/openssl/stack.h",
+ "include/openssl/stack_macros.h",
+ "include/openssl/thread.h",
+ "include/openssl/time_support.h",
+ "include/openssl/type_check.h",
+ "include/openssl/x509.h",
+ "include/openssl/x509_vfy.h",
+ "include/openssl/x509v3.h",
+]
+
+crypto_internal_headers = [
+ "crypto/aes/internal.h",
+ "crypto/asn1/asn1_locl.h",
+ "crypto/bio/internal.h",
+ "crypto/bn/internal.h",
+ "crypto/bn/rsaz_exp.h",
+ "crypto/bytestring/internal.h",
+ "crypto/cipher/internal.h",
+ "crypto/conf/conf_def.h",
+ "crypto/conf/internal.h",
+ "crypto/curve25519/internal.h",
+ "crypto/des/internal.h",
+ "crypto/dh/internal.h",
+ "crypto/digest/internal.h",
+ "crypto/digest/md32_common.h",
+ "crypto/ec/internal.h",
+ "crypto/ec/p256-x86_64-table.h",
+ "crypto/evp/internal.h",
+ "crypto/internal.h",
+ "crypto/modes/internal.h",
+ "crypto/newhope/internal.h",
+ "crypto/obj/obj_dat.h",
+ "crypto/obj/obj_xref.h",
+ "crypto/pkcs8/internal.h",
+ "crypto/poly1305/internal.h",
+ "crypto/rand/internal.h",
+ "crypto/rsa/internal.h",
+ "crypto/test/scoped_types.h",
+ "crypto/test/test_util.h",
+ "crypto/x509/charmap.h",
+ "crypto/x509/internal.h",
+ "crypto/x509/vpm_int.h",
+ "crypto/x509v3/ext_dat.h",
+ "crypto/x509v3/pcy_int.h",
+]
+
+crypto_sources = [
+ ":err_data_c",
+ "crypto/aes/aes.c",
+ "crypto/aes/mode_wrappers.c",
+ "crypto/asn1/a_bitstr.c",
+ "crypto/asn1/a_bool.c",
+ "crypto/asn1/a_bytes.c",
+ "crypto/asn1/a_d2i_fp.c",
+ "crypto/asn1/a_dup.c",
+ "crypto/asn1/a_enum.c",
+ "crypto/asn1/a_gentm.c",
+ "crypto/asn1/a_i2d_fp.c",
+ "crypto/asn1/a_int.c",
+ "crypto/asn1/a_mbstr.c",
+ "crypto/asn1/a_object.c",
+ "crypto/asn1/a_octet.c",
+ "crypto/asn1/a_print.c",
+ "crypto/asn1/a_strnid.c",
+ "crypto/asn1/a_time.c",
+ "crypto/asn1/a_type.c",
+ "crypto/asn1/a_utctm.c",
+ "crypto/asn1/a_utf8.c",
+ "crypto/asn1/asn1_lib.c",
+ "crypto/asn1/asn1_par.c",
+ "crypto/asn1/asn_pack.c",
+ "crypto/asn1/bio_asn1.c",
+ "crypto/asn1/bio_ndef.c",
+ "crypto/asn1/f_enum.c",
+ "crypto/asn1/f_int.c",
+ "crypto/asn1/f_string.c",
+ "crypto/asn1/t_bitst.c",
+ "crypto/asn1/tasn_dec.c",
+ "crypto/asn1/tasn_enc.c",
+ "crypto/asn1/tasn_fre.c",
+ "crypto/asn1/tasn_new.c",
+ "crypto/asn1/tasn_prn.c",
+ "crypto/asn1/tasn_typ.c",
+ "crypto/asn1/tasn_utl.c",
+ "crypto/asn1/x_bignum.c",
+ "crypto/asn1/x_long.c",
+ "crypto/base64/base64.c",
+ "crypto/bio/bio.c",
+ "crypto/bio/bio_mem.c",
+ "crypto/bio/buffer.c",
+ "crypto/bio/connect.c",
+ "crypto/bio/fd.c",
+ "crypto/bio/file.c",
+ "crypto/bio/hexdump.c",
+ "crypto/bio/pair.c",
+ "crypto/bio/printf.c",
+ "crypto/bio/socket.c",
+ "crypto/bio/socket_helper.c",
+ "crypto/bn/add.c",
+ "crypto/bn/asm/x86_64-gcc.c",
+ "crypto/bn/bn.c",
+ "crypto/bn/bn_asn1.c",
+ "crypto/bn/cmp.c",
+ "crypto/bn/convert.c",
+ "crypto/bn/ctx.c",
+ "crypto/bn/div.c",
+ "crypto/bn/exponentiation.c",
+ "crypto/bn/gcd.c",
+ "crypto/bn/generic.c",
+ "crypto/bn/kronecker.c",
+ "crypto/bn/montgomery.c",
+ "crypto/bn/mul.c",
+ "crypto/bn/prime.c",
+ "crypto/bn/random.c",
+ "crypto/bn/rsaz_exp.c",
+ "crypto/bn/shift.c",
+ "crypto/bn/sqrt.c",
+ "crypto/buf/buf.c",
+ "crypto/bytestring/asn1_compat.c",
+ "crypto/bytestring/ber.c",
+ "crypto/bytestring/cbb.c",
+ "crypto/bytestring/cbs.c",
+ "crypto/chacha/chacha.c",
+ "crypto/cipher/aead.c",
+ "crypto/cipher/cipher.c",
+ "crypto/cipher/derive_key.c",
+ "crypto/cipher/e_aes.c",
+ "crypto/cipher/e_chacha20poly1305.c",
+ "crypto/cipher/e_des.c",
+ "crypto/cipher/e_null.c",
+ "crypto/cipher/e_rc2.c",
+ "crypto/cipher/e_rc4.c",
+ "crypto/cipher/e_ssl3.c",
+ "crypto/cipher/e_tls.c",
+ "crypto/cipher/tls_cbc.c",
+ "crypto/cmac/cmac.c",
+ "crypto/conf/conf.c",
+ "crypto/cpu-aarch64-linux.c",
+ "crypto/cpu-arm-linux.c",
+ "crypto/cpu-arm.c",
+ "crypto/cpu-intel.c",
+ "crypto/crypto.c",
+ "crypto/curve25519/curve25519.c",
+ "crypto/curve25519/spake25519.c",
+ "crypto/curve25519/x25519-x86_64.c",
+ "crypto/des/des.c",
+ "crypto/dh/check.c",
+ "crypto/dh/dh.c",
+ "crypto/dh/dh_asn1.c",
+ "crypto/dh/params.c",
+ "crypto/digest/digest.c",
+ "crypto/digest/digests.c",
+ "crypto/dsa/dsa.c",
+ "crypto/dsa/dsa_asn1.c",
+ "crypto/ec/ec.c",
+ "crypto/ec/ec_asn1.c",
+ "crypto/ec/ec_key.c",
+ "crypto/ec/ec_montgomery.c",
+ "crypto/ec/oct.c",
+ "crypto/ec/p224-64.c",
+ "crypto/ec/p256-64.c",
+ "crypto/ec/p256-x86_64.c",
+ "crypto/ec/simple.c",
+ "crypto/ec/util-64.c",
+ "crypto/ec/wnaf.c",
+ "crypto/ecdh/ecdh.c",
+ "crypto/ecdsa/ecdsa.c",
+ "crypto/ecdsa/ecdsa_asn1.c",
+ "crypto/engine/engine.c",
+ "crypto/err/err.c",
+ "crypto/evp/digestsign.c",
+ "crypto/evp/evp.c",
+ "crypto/evp/evp_asn1.c",
+ "crypto/evp/evp_ctx.c",
+ "crypto/evp/p_dsa_asn1.c",
+ "crypto/evp/p_ec.c",
+ "crypto/evp/p_ec_asn1.c",
+ "crypto/evp/p_rsa.c",
+ "crypto/evp/p_rsa_asn1.c",
+ "crypto/evp/pbkdf.c",
+ "crypto/evp/print.c",
+ "crypto/evp/sign.c",
+ "crypto/ex_data.c",
+ "crypto/hkdf/hkdf.c",
+ "crypto/hmac/hmac.c",
+ "crypto/lhash/lhash.c",
+ "crypto/md4/md4.c",
+ "crypto/md5/md5.c",
+ "crypto/mem.c",
+ "crypto/modes/cbc.c",
+ "crypto/modes/cfb.c",
+ "crypto/modes/ctr.c",
+ "crypto/modes/gcm.c",
+ "crypto/modes/ofb.c",
+ "crypto/newhope/error_correction.c",
+ "crypto/newhope/newhope.c",
+ "crypto/newhope/ntt.c",
+ "crypto/newhope/poly.c",
+ "crypto/newhope/precomp.c",
+ "crypto/newhope/reduce.c",
+ "crypto/obj/obj.c",
+ "crypto/obj/obj_xref.c",
+ "crypto/pem/pem_all.c",
+ "crypto/pem/pem_info.c",
+ "crypto/pem/pem_lib.c",
+ "crypto/pem/pem_oth.c",
+ "crypto/pem/pem_pk8.c",
+ "crypto/pem/pem_pkey.c",
+ "crypto/pem/pem_x509.c",
+ "crypto/pem/pem_xaux.c",
+ "crypto/pkcs8/p5_pbe.c",
+ "crypto/pkcs8/p5_pbev2.c",
+ "crypto/pkcs8/p8_pkey.c",
+ "crypto/pkcs8/pkcs8.c",
+ "crypto/poly1305/poly1305.c",
+ "crypto/poly1305/poly1305_arm.c",
+ "crypto/poly1305/poly1305_vec.c",
+ "crypto/rand/deterministic.c",
+ "crypto/rand/rand.c",
+ "crypto/rand/urandom.c",
+ "crypto/rand/windows.c",
+ "crypto/rc4/rc4.c",
+ "crypto/refcount_c11.c",
+ "crypto/refcount_lock.c",
+ "crypto/rsa/blinding.c",
+ "crypto/rsa/padding.c",
+ "crypto/rsa/rsa.c",
+ "crypto/rsa/rsa_asn1.c",
+ "crypto/rsa/rsa_impl.c",
+ "crypto/sha/sha1.c",
+ "crypto/sha/sha256.c",
+ "crypto/sha/sha512.c",
+ "crypto/stack/stack.c",
+ "crypto/thread.c",
+ "crypto/thread_none.c",
+ "crypto/thread_pthread.c",
+ "crypto/thread_win.c",
+ "crypto/time_support.c",
+ "crypto/x509/a_digest.c",
+ "crypto/x509/a_sign.c",
+ "crypto/x509/a_strex.c",
+ "crypto/x509/a_verify.c",
+ "crypto/x509/algorithm.c",
+ "crypto/x509/asn1_gen.c",
+ "crypto/x509/by_dir.c",
+ "crypto/x509/by_file.c",
+ "crypto/x509/i2d_pr.c",
+ "crypto/x509/pkcs7.c",
+ "crypto/x509/rsa_pss.c",
+ "crypto/x509/t_crl.c",
+ "crypto/x509/t_req.c",
+ "crypto/x509/t_x509.c",
+ "crypto/x509/t_x509a.c",
+ "crypto/x509/x509.c",
+ "crypto/x509/x509_att.c",
+ "crypto/x509/x509_cmp.c",
+ "crypto/x509/x509_d2.c",
+ "crypto/x509/x509_def.c",
+ "crypto/x509/x509_ext.c",
+ "crypto/x509/x509_lu.c",
+ "crypto/x509/x509_obj.c",
+ "crypto/x509/x509_r2x.c",
+ "crypto/x509/x509_req.c",
+ "crypto/x509/x509_set.c",
+ "crypto/x509/x509_trs.c",
+ "crypto/x509/x509_txt.c",
+ "crypto/x509/x509_v3.c",
+ "crypto/x509/x509_vfy.c",
+ "crypto/x509/x509_vpm.c",
+ "crypto/x509/x509cset.c",
+ "crypto/x509/x509name.c",
+ "crypto/x509/x509rset.c",
+ "crypto/x509/x509spki.c",
+ "crypto/x509/x509type.c",
+ "crypto/x509/x_algor.c",
+ "crypto/x509/x_all.c",
+ "crypto/x509/x_attrib.c",
+ "crypto/x509/x_crl.c",
+ "crypto/x509/x_exten.c",
+ "crypto/x509/x_info.c",
+ "crypto/x509/x_name.c",
+ "crypto/x509/x_pkey.c",
+ "crypto/x509/x_pubkey.c",
+ "crypto/x509/x_req.c",
+ "crypto/x509/x_sig.c",
+ "crypto/x509/x_spki.c",
+ "crypto/x509/x_val.c",
+ "crypto/x509/x_x509.c",
+ "crypto/x509/x_x509a.c",
+ "crypto/x509v3/pcy_cache.c",
+ "crypto/x509v3/pcy_data.c",
+ "crypto/x509v3/pcy_lib.c",
+ "crypto/x509v3/pcy_map.c",
+ "crypto/x509v3/pcy_node.c",
+ "crypto/x509v3/pcy_tree.c",
+ "crypto/x509v3/v3_akey.c",
+ "crypto/x509v3/v3_akeya.c",
+ "crypto/x509v3/v3_alt.c",
+ "crypto/x509v3/v3_bcons.c",
+ "crypto/x509v3/v3_bitst.c",
+ "crypto/x509v3/v3_conf.c",
+ "crypto/x509v3/v3_cpols.c",
+ "crypto/x509v3/v3_crld.c",
+ "crypto/x509v3/v3_enum.c",
+ "crypto/x509v3/v3_extku.c",
+ "crypto/x509v3/v3_genn.c",
+ "crypto/x509v3/v3_ia5.c",
+ "crypto/x509v3/v3_info.c",
+ "crypto/x509v3/v3_int.c",
+ "crypto/x509v3/v3_lib.c",
+ "crypto/x509v3/v3_ncons.c",
+ "crypto/x509v3/v3_pci.c",
+ "crypto/x509v3/v3_pcia.c",
+ "crypto/x509v3/v3_pcons.c",
+ "crypto/x509v3/v3_pku.c",
+ "crypto/x509v3/v3_pmaps.c",
+ "crypto/x509v3/v3_prn.c",
+ "crypto/x509v3/v3_purp.c",
+ "crypto/x509v3/v3_skey.c",
+ "crypto/x509v3/v3_sxnet.c",
+ "crypto/x509v3/v3_utl.c",
+]
+
+# A trick to take the generated err_data.c from another package.
+genrule(
+ name = "err_data_c",
+ srcs = ["@//third_party/boringssl:err_data_c"],
+ outs = ["err_data.c"],
+ cmd = "cp $< $@",
+)
+
+cc_library(
+ name = "crypto",
+ srcs = crypto_internal_headers + crypto_sources,
+ hdrs = crypto_headers,
+ # To avoid linking platform-specific ASM files.
+ defines = ["OPENSSL_NO_ASM"],
+ includes = ["include"],
+ visibility = ["//visibility:public"],
+)
diff --git a/tensorflow/core/platform/cloud/BUILD b/tensorflow/core/platform/cloud/BUILD
index 95da137386..a47e772dfd 100644
--- a/tensorflow/core/platform/cloud/BUILD
+++ b/tensorflow/core/platform/cloud/BUILD
@@ -36,6 +36,7 @@ cc_library(
visibility = ["//visibility:public"],
deps = [
"@jsoncpp_git//:jsoncpp",
+ ":google_auth_provider",
":http_request",
"//tensorflow/core:framework_headers_lib",
"//tensorflow/core:lib_internal",
@@ -57,11 +58,74 @@ cc_library(
],
)
+cc_library(
+ name = "http_request_fake",
+ testonly = 1,
+ hdrs = [
+ "http_request_fake.h",
+ ],
+ deps = [
+ ":http_request",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:test",
+ ],
+)
+
+cc_library(
+ name = "google_auth_provider",
+ srcs = [
+ "google_auth_provider.cc",
+ ],
+ hdrs = [
+ "auth_provider.h",
+ "google_auth_provider.h",
+ ],
+ deps = [
+ "@jsoncpp_git//:jsoncpp",
+ ":base64",
+ ":http_request",
+ ":oauth_client",
+ "//tensorflow/core:lib",
+ ],
+)
+
+cc_library(
+ name = "oauth_client",
+ srcs = [
+ "oauth_client.cc",
+ ],
+ hdrs = [
+ "oauth_client.h",
+ ],
+ deps = [
+ "@boringssl_git//:crypto",
+ "@jsoncpp_git//:jsoncpp",
+ ":base64",
+ ":http_request",
+ "//tensorflow/core:lib",
+ ],
+)
+
+cc_library(
+ name = "base64",
+ srcs = [
+ "base64.cc",
+ ],
+ hdrs = [
+ "base64.h",
+ ],
+ deps = [
+ "//tensorflow/core:lib",
+ ],
+)
+
tf_cc_test(
name = "gcs_file_system_test",
size = "small",
deps = [
":gcs_file_system",
+ ":http_request_fake",
"//tensorflow/core:test",
"//tensorflow/core:test_main",
],
@@ -77,3 +141,53 @@ tf_cc_test(
"//tensorflow/core:test_main",
],
)
+
+tf_cc_test(
+ name = "oauth_client_test",
+ size = "small",
+ data = [
+ "testdata/service_account_credentials.json",
+ "testdata/service_account_public_key.txt",
+ ],
+ deps = [
+ "@boringssl_git//:crypto",
+ ":base64",
+ ":http_request_fake",
+ ":oauth_client",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ ],
+)
+
+tf_cc_test(
+ name = "base64_test",
+ size = "small",
+ deps = [
+ ":base64",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ ],
+)
+
+tf_cc_test(
+ name = "google_auth_provider_test",
+ size = "small",
+ data = [
+ "testdata/application_default_credentials.json",
+ "testdata/service_account_credentials.json",
+ ],
+ deps = [
+ ":base64",
+ ":google_auth_provider",
+ ":http_request_fake",
+ ":oauth_client",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ ],
+)
diff --git a/tensorflow/core/platform/cloud/auth_provider.h b/tensorflow/core/platform/cloud/auth_provider.h
new file mode 100644
index 0000000000..88636cba4e
--- /dev/null
+++ b/tensorflow/core/platform/cloud/auth_provider.h
@@ -0,0 +1,52 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_AUTH_PROVIDER_H_
+#define TENSORFLOW_CORE_PLATFORM_AUTH_PROVIDER_H_
+
+#include <string>
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/lib/core/status.h"
+
+namespace tensorflow {
+
+/// Interface for a provider of authentication bearer tokens.
+class AuthProvider {
+ public:
+ virtual ~AuthProvider() {}
+
+ /// Returns the short-term authentication bearer token.
+ virtual Status GetToken(string* t) = 0;
+
+ static Status GetToken(AuthProvider* provider, string* token) {
+ if (!provider) {
+ return errors::Internal("Auth provider is required.");
+ }
+ return provider->GetToken(token);
+ }
+};
+
+/// No-op auth provider, which will only work for public objects.
+class EmptyAuthProvider : public AuthProvider {
+ public:
+ Status GetToken(string* token) override {
+ *token = "";
+ return Status::OK();
+ }
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_AUTH_PROVIDER_H_
diff --git a/tensorflow/core/platform/cloud/base64.cc b/tensorflow/core/platform/cloud/base64.cc
new file mode 100644
index 0000000000..c19f04217d
--- /dev/null
+++ b/tensorflow/core/platform/cloud/base64.cc
@@ -0,0 +1,221 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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 "tensorflow/core/platform/cloud/base64.h"
+#include <memory>
+#include "tensorflow/core/lib/core/errors.h"
+
+namespace tensorflow {
+
+namespace {
+
+constexpr signed char kBase64Bytes[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 0x3E, -1, -1, -1, 0x3F,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, -1, -1,
+ -1, 0x7F, -1, -1, -1, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, -1, -1, -1, -1, -1,
+ -1, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
+ 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
+ 0x31, 0x32, 0x33, -1, -1, -1, -1, -1};
+
+constexpr char kBase64UrlSafeChars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+constexpr char kPadChar = '=';
+constexpr char kPadByte = 0x7F;
+constexpr int kMultilineLineLen = 76;
+constexpr int kMultilineNumBlocks = kMultilineLineLen / 4;
+
+Status Base64Encode(StringPiece source, bool multiline, bool with_padding,
+ string *encoded) {
+ if (!encoded) {
+ return errors::FailedPrecondition("'encoded' cannot be nullptr.");
+ }
+ size_t data_size = source.size();
+ const char *data = source.data();
+ const char *base64_chars = kBase64UrlSafeChars;
+ const size_t result_projected_size =
+ 4 * ((data_size + 3) / 3) +
+ 2 * (multiline ? (data_size / (3 * kMultilineNumBlocks)) : 0) + 1;
+ size_t num_blocks = 0;
+ size_t i = 0;
+ std::unique_ptr<char[]> result(new char[result_projected_size]);
+ char *current = result.get();
+
+ /* Encode each block. */
+ while (data_size >= 3) {
+ *current++ = base64_chars[(data[i] >> 2) & 0x3F];
+ *current++ =
+ base64_chars[((data[i] & 0x03) << 4) | ((data[i + 1] >> 4) & 0x0F)];
+ *current++ =
+ base64_chars[((data[i + 1] & 0x0F) << 2) | ((data[i + 2] >> 6) & 0x03)];
+ *current++ = base64_chars[data[i + 2] & 0x3F];
+
+ data_size -= 3;
+ i += 3;
+ if (multiline && (++num_blocks == kMultilineNumBlocks)) {
+ *current++ = '\r';
+ *current++ = '\n';
+ num_blocks = 0;
+ }
+ }
+
+ /* Take care of the tail. */
+ if (data_size == 2) {
+ *current++ = base64_chars[(data[i] >> 2) & 0x3F];
+ *current++ =
+ base64_chars[((data[i] & 0x03) << 4) | ((data[i + 1] >> 4) & 0x0F)];
+ *current++ = base64_chars[(data[i + 1] & 0x0F) << 2];
+ if (with_padding) {
+ *current++ = kPadChar;
+ }
+ } else if (data_size == 1) {
+ *current++ = base64_chars[(data[i] >> 2) & 0x3F];
+ *current++ = base64_chars[(data[i] & 0x03) << 4];
+ if (with_padding) {
+ *current++ = kPadChar;
+ *current++ = kPadChar;
+ }
+ }
+
+ if (current < result.get() ||
+ current >= result.get() + result_projected_size) {
+ return errors::Internal("Unexpected encoding bug.");
+ }
+ *current++ = '\0';
+ *encoded = result.get();
+ return Status::OK();
+}
+
+void DecodeOneChar(const unsigned char *codes, unsigned char *result,
+ size_t *result_offset) {
+ const uint32_t packed = ((uint32_t)codes[0] << 2) | ((uint32_t)codes[1] >> 4);
+ result[(*result_offset)++] = (unsigned char)packed;
+}
+
+void DecodeTwoChars(const unsigned char *codes, unsigned char *result,
+ size_t *result_offset) {
+ const uint32_t packed = ((uint32_t)codes[0] << 10) |
+ ((uint32_t)codes[1] << 4) | ((uint32_t)codes[2] >> 2);
+ result[(*result_offset)++] = (unsigned char)(packed >> 8);
+ result[(*result_offset)++] = (unsigned char)(packed);
+}
+
+Status DecodeGroup(const unsigned char *codes, size_t num_codes,
+ unsigned char *result, size_t *result_offset) {
+ if (num_codes > 4) {
+ return errors::FailedPrecondition("Expected 4 or fewer codes.");
+ }
+
+ /* Short end groups that may not have padding. */
+ if (num_codes == 1) {
+ return errors::FailedPrecondition(
+ "Invalid group. Must be at least 2 bytes.");
+ }
+ if (num_codes == 2) {
+ DecodeOneChar(codes, result, result_offset);
+ return Status::OK();
+ }
+ if (num_codes == 3) {
+ DecodeTwoChars(codes, result, result_offset);
+ return Status::OK();
+ }
+
+ /* Regular 4 byte groups with padding or not. */
+ if (num_codes != 4) {
+ return errors::FailedPrecondition("Expected exactly 4 codes.");
+ }
+ if (codes[0] == kPadByte || codes[1] == kPadByte) {
+ return errors::FailedPrecondition("Invalid padding detected.");
+ }
+ if (codes[2] == kPadByte) {
+ if (codes[3] == kPadByte) {
+ DecodeOneChar(codes, result, result_offset);
+ } else {
+ return errors::FailedPrecondition("Invalid padding detected.");
+ }
+ } else if (codes[3] == kPadByte) {
+ DecodeTwoChars(codes, result, result_offset);
+ } else {
+ /* No padding. */
+ const uint32_t packed = ((uint32_t)codes[0] << 18) |
+ ((uint32_t)codes[1] << 12) |
+ ((uint32_t)codes[2] << 6) | codes[3];
+ result[(*result_offset)++] = (unsigned char)(packed >> 16);
+ result[(*result_offset)++] = (unsigned char)(packed >> 8);
+ result[(*result_offset)++] = (unsigned char)(packed);
+ }
+ return Status::OK();
+}
+
+} // namespace
+
+Status Base64Encode(StringPiece source, string *encoded) {
+ return Base64Encode(source, false, false, encoded);
+}
+
+Status Base64Decode(StringPiece data, string *decoded) {
+ if (!decoded) {
+ return errors::FailedPrecondition("'decoded' cannot be nullptr.");
+ }
+ std::unique_ptr<unsigned char[]> result(new unsigned char[data.size()]);
+ unsigned char *current = result.get();
+ size_t result_size = 0;
+ unsigned char codes[4];
+ size_t num_codes = 0;
+
+ const char *b64 = data.data();
+ size_t b64_len = data.size();
+ while (b64_len--) {
+ unsigned char c = (unsigned char)(*b64++);
+ signed char code;
+ if (c >= sizeof(kBase64Bytes)) continue;
+ if (c == '+' || c == '/') {
+ return errors::FailedPrecondition(
+ strings::StrCat("Invalid character for url safe base64 ", c));
+ }
+ if (c == '-') {
+ c = '+';
+ } else if (c == '_') {
+ c = '/';
+ }
+ code = kBase64Bytes[c];
+ if (code == -1) {
+ if (c != '\r' && c != '\n') {
+ return errors::FailedPrecondition(
+ strings::StrCat("Invalid character ", c));
+ }
+ } else {
+ codes[num_codes++] = (unsigned char)code;
+ if (num_codes == 4) {
+ TF_RETURN_IF_ERROR(
+ DecodeGroup(codes, num_codes, current, &result_size));
+ num_codes = 0;
+ }
+ }
+ }
+
+ if (num_codes != 0) {
+ TF_RETURN_IF_ERROR(DecodeGroup(codes, num_codes, current, &result_size));
+ }
+ *decoded = string(reinterpret_cast<char *>(result.get()), result_size);
+ return Status::OK();
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/base64.h b/tensorflow/core/platform/cloud/base64.h
new file mode 100644
index 0000000000..4e495a243f
--- /dev/null
+++ b/tensorflow/core/platform/cloud/base64.h
@@ -0,0 +1,36 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_B64_H_
+#define TENSORFLOW_CORE_PLATFORM_B64_H_
+
+#include <string>
+#include "tensorflow/core/lib/core/status.h"
+
+namespace tensorflow {
+
+/// \brief Converts data into base64 encoding.
+///
+/// See https://en.wikipedia.org/wiki/Base64
+Status Base64Encode(StringPiece data, string* encoded);
+
+/// \brief Converts data from base64 encoding.
+///
+/// See https://en.wikipedia.org/wiki/Base64
+Status Base64Decode(StringPiece data, string* decoded);
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_B64_H_
diff --git a/tensorflow/core/platform/cloud/base64_test.cc b/tensorflow/core/platform/cloud/base64_test.cc
new file mode 100644
index 0000000000..65fd738a9b
--- /dev/null
+++ b/tensorflow/core/platform/cloud/base64_test.cc
@@ -0,0 +1,33 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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 "tensorflow/core/platform/cloud/base64.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+
+TEST(Base64, EncodeDecode) {
+ const string original = "a simple test message!";
+ string encoded;
+ TF_EXPECT_OK(Base64Encode(original, &encoded));
+ EXPECT_EQ("YSBzaW1wbGUgdGVzdCBtZXNzYWdlIQ", encoded);
+
+ string decoded;
+ TF_EXPECT_OK(Base64Decode(encoded, &decoded));
+ EXPECT_EQ(original, decoded);
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/gcs_file_system.cc b/tensorflow/core/platform/cloud/gcs_file_system.cc
index ba9418f06f..76cb80afb3 100644
--- a/tensorflow/core/platform/cloud/gcs_file_system.cc
+++ b/tensorflow/core/platform/cloud/gcs_file_system.cc
@@ -28,6 +28,7 @@ limitations under the License.
#include "tensorflow/core/lib/strings/numbers.h"
#include "tensorflow/core/lib/strings/scanner.h"
#include "tensorflow/core/lib/strings/str_util.h"
+#include "tensorflow/core/platform/cloud/google_auth_provider.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/protobuf.h"
#include "tensorflow/core/platform/regexp.h"
@@ -56,22 +57,6 @@ Status GetTmpFilename(string* filename) {
return Status::OK();
}
-/// No-op auth provider, which will only work for public objects.
-class EmptyAuthProvider : public AuthProvider {
- public:
- Status GetToken(string* token) const override {
- *token = "";
- return Status::OK();
- }
-};
-
-Status GetAuthToken(const AuthProvider* provider, string* token) {
- if (!provider) {
- return errors::Internal("Auth provider is required.");
- }
- return provider->GetToken(token);
-}
-
/// \brief Splits a GCS path to a bucket and an object.
///
/// For example, "gs://bucket-name/path/to/file.txt" gets split into
@@ -109,7 +94,7 @@ class GcsRandomAccessFile : public RandomAccessFile {
Status Read(uint64 offset, size_t n, StringPiece* result,
char* scratch) const override {
string auth_token;
- TF_RETURN_IF_ERROR(GetAuthToken(auth_provider_, &auth_token));
+ TF_RETURN_IF_ERROR(AuthProvider::GetToken(auth_provider_, &auth_token));
std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
TF_RETURN_IF_ERROR(request->Init());
@@ -198,7 +183,7 @@ class GcsWritableFile : public WritableFile {
TF_RETURN_IF_ERROR(CheckWritable());
outfile_.flush();
string auth_token;
- TF_RETURN_IF_ERROR(GetAuthToken(auth_provider_, &auth_token));
+ TF_RETURN_IF_ERROR(AuthProvider::GetToken(auth_provider_, &auth_token));
std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
TF_RETURN_IF_ERROR(request->Init());
@@ -242,7 +227,7 @@ class GcsReadOnlyMemoryRegion : public ReadOnlyMemoryRegion {
} // namespace
GcsFileSystem::GcsFileSystem()
- : auth_provider_(new EmptyAuthProvider()),
+ : auth_provider_(new GoogleAuthProvider()),
http_request_factory_(new HttpRequest::Factory()) {}
GcsFileSystem::GcsFileSystem(
@@ -334,7 +319,7 @@ bool GcsFileSystem::FileExists(const string& fname) {
}
string auth_token;
- if (!GetAuthToken(auth_provider_.get(), &auth_token).ok()) {
+ if (!AuthProvider::GetToken(auth_provider_.get(), &auth_token).ok()) {
LOG(ERROR) << "Could not get an auth token.";
return false;
}
@@ -363,7 +348,7 @@ Status GcsFileSystem::GetChildren(const string& dirname,
TF_RETURN_IF_ERROR(ParseGcsPath(sanitized_dirname, &bucket, &object_prefix));
string auth_token;
- TF_RETURN_IF_ERROR(GetAuthToken(auth_provider_.get(), &auth_token));
+ TF_RETURN_IF_ERROR(AuthProvider::GetToken(auth_provider_.get(), &auth_token));
std::unique_ptr<char[]> scratch(new char[kBufferSize]);
StringPiece response_piece;
@@ -417,7 +402,7 @@ Status GcsFileSystem::DeleteFile(const string& fname) {
TF_RETURN_IF_ERROR(ParseGcsPath(fname, &bucket, &object));
string auth_token;
- TF_RETURN_IF_ERROR(GetAuthToken(auth_provider_.get(), &auth_token));
+ TF_RETURN_IF_ERROR(AuthProvider::GetToken(auth_provider_.get(), &auth_token));
std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
TF_RETURN_IF_ERROR(request->Init());
@@ -452,7 +437,7 @@ Status GcsFileSystem::GetFileSize(const string& fname, uint64* file_size) {
TF_RETURN_IF_ERROR(ParseGcsPath(fname, &bucket, &object_prefix));
string auth_token;
- TF_RETURN_IF_ERROR(GetAuthToken(auth_provider_.get(), &auth_token));
+ TF_RETURN_IF_ERROR(AuthProvider::GetToken(auth_provider_.get(), &auth_token));
std::unique_ptr<char[]> scratch(new char[kBufferSize]);
StringPiece response_piece;
@@ -496,7 +481,7 @@ Status GcsFileSystem::RenameFile(const string& src, const string& target) {
TF_RETURN_IF_ERROR(ParseGcsPath(target, &target_bucket, &target_object));
string auth_token;
- TF_RETURN_IF_ERROR(GetAuthToken(auth_provider_.get(), &auth_token));
+ TF_RETURN_IF_ERROR(AuthProvider::GetToken(auth_provider_.get(), &auth_token));
std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
TF_RETURN_IF_ERROR(request->Init());
diff --git a/tensorflow/core/platform/cloud/gcs_file_system.h b/tensorflow/core/platform/cloud/gcs_file_system.h
index 47c22173de..9e71455cb6 100644
--- a/tensorflow/core/platform/cloud/gcs_file_system.h
+++ b/tensorflow/core/platform/cloud/gcs_file_system.h
@@ -19,18 +19,12 @@ limitations under the License.
#include <string>
#include <vector>
#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/platform/cloud/auth_provider.h"
#include "tensorflow/core/platform/cloud/http_request.h"
#include "tensorflow/core/platform/file_system.h"
namespace tensorflow {
-/// Interface for a provider of HTTP auth bearer tokens.
-class AuthProvider {
- public:
- virtual ~AuthProvider() {}
- virtual Status GetToken(string* t) const = 0;
-};
-
/// Google Cloud Storage implementation of a file system.
class GcsFileSystem : public FileSystem {
public:
diff --git a/tensorflow/core/platform/cloud/gcs_file_system_test.cc b/tensorflow/core/platform/cloud/gcs_file_system_test.cc
index 151aebd87c..4505000b61 100644
--- a/tensorflow/core/platform/cloud/gcs_file_system_test.cc
+++ b/tensorflow/core/platform/cloud/gcs_file_system_test.cc
@@ -16,101 +16,15 @@ limitations under the License.
#include "tensorflow/core/platform/cloud/gcs_file_system.h"
#include <fstream>
#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/platform/cloud/http_request_fake.h"
#include "tensorflow/core/platform/test.h"
namespace tensorflow {
namespace {
-class FakeHttpRequest : public HttpRequest {
- public:
- FakeHttpRequest(const string& request, const string& response)
- : FakeHttpRequest(request, response, Status::OK()) {}
-
- FakeHttpRequest(const string& request, const string& response,
- Status response_status)
- : expected_request_(request),
- response_(response),
- response_status_(response_status) {}
-
- Status Init() override { return Status::OK(); }
- Status SetUri(const string& uri) override {
- actual_request_ += "Uri: " + uri + "\n";
- return Status::OK();
- }
- Status SetRange(uint64 start, uint64 end) override {
- actual_request_ += strings::StrCat("Range: ", start, "-", end, "\n");
- return Status::OK();
- }
- Status AddAuthBearerHeader(const string& auth_token) override {
- actual_request_ += "Auth Token: " + auth_token + "\n";
- return Status::OK();
- }
- Status SetDeleteRequest() override {
- actual_request_ += "Delete: yes\n";
- return Status::OK();
- }
- Status SetPostRequest(const string& body_filepath) override {
- std::ifstream stream(body_filepath);
- string content((std::istreambuf_iterator<char>(stream)),
- std::istreambuf_iterator<char>());
- actual_request_ += "Post body: " + content + "\n";
- return Status::OK();
- }
- Status SetPostRequest() override {
- actual_request_ += "Post: yes\n";
- return Status::OK();
- }
- Status SetResultBuffer(char* scratch, size_t size,
- StringPiece* result) override {
- scratch_ = scratch;
- size_ = size;
- result_ = result;
- return Status::OK();
- }
- Status Send() override {
- EXPECT_EQ(expected_request_, actual_request_) << "Unexpected HTTP request.";
- if (scratch_ && result_) {
- auto actual_size = std::min(response_.size(), size_);
- memcpy(scratch_, response_.c_str(), actual_size);
- *result_ = StringPiece(scratch_, actual_size);
- }
- return response_status_;
- }
-
- private:
- char* scratch_ = nullptr;
- size_t size_ = 0;
- StringPiece* result_ = nullptr;
- string expected_request_;
- string actual_request_;
- string response_;
- Status response_status_;
-};
-
-class FakeHttpRequestFactory : public HttpRequest::Factory {
- public:
- FakeHttpRequestFactory(const std::vector<HttpRequest*>* requests)
- : requests_(requests) {}
-
- ~FakeHttpRequestFactory() {
- EXPECT_EQ(current_index_, requests_->size())
- << "Not all expected requests were made.";
- }
-
- HttpRequest* Create() override {
- EXPECT_LT(current_index_, requests_->size())
- << "Too many calls of HttpRequest factory.";
- return (*requests_)[current_index_++];
- }
-
- private:
- const std::vector<HttpRequest*>* requests_;
- int current_index_ = 0;
-};
-
class FakeAuthProvider : public AuthProvider {
public:
- Status GetToken(string* token) const override {
+ Status GetToken(string* token) override {
*token = "fake_token";
return Status::OK();
}
diff --git a/tensorflow/core/platform/cloud/google_auth_provider.cc b/tensorflow/core/platform/cloud/google_auth_provider.cc
new file mode 100644
index 0000000000..50b3a9eb1d
--- /dev/null
+++ b/tensorflow/core/platform/cloud/google_auth_provider.cc
@@ -0,0 +1,190 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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 "tensorflow/core/platform/cloud/google_auth_provider.h"
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fstream>
+#include "include/json/json.h"
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/lib/io/path.h"
+#include "tensorflow/core/platform/cloud/base64.h"
+#include "tensorflow/core/platform/cloud/http_request.h"
+#include "tensorflow/core/platform/env.h"
+
+namespace tensorflow {
+
+namespace {
+
+// The environment variable pointing to the file with local
+// Application Default Credentials.
+constexpr char kGoogleApplicationCredentials[] =
+ "GOOGLE_APPLICATION_CREDENTIALS";
+
+// The environment variable which can override '~/.config/gcloud' if set.
+constexpr char kCloudSdkConfig[] = "CLOUDSDK_CONFIG";
+
+// The default path to the gcloud config folder, relative to the home folder.
+constexpr char kGCloudConfigFolder[] = ".config/gcloud/";
+
+// The name of the well-known credentials JSON file in the gcloud config folder.
+constexpr char kWellKnownCredentialsFile[] =
+ "application_default_credentials.json";
+
+// The minimum time delta between now and the token expiration time
+// for the token to be re-used.
+constexpr int kExpirationTimeMarginSec = 10;
+
+// The URL to retrieve the auth bearer token via OAuth with a refresh token.
+constexpr char kOAuthV3Url[] = "https://www.googleapis.com/oauth2/v3/token";
+
+// The URL to retrieve the auth bearer token via OAuth with a private key.
+constexpr char kOAuthV4Url[] = "https://www.googleapis.com/oauth2/v4/token";
+
+// The URL to retrieve the auth bearer token when running in Google Compute
+// Engine.
+constexpr char kGceTokenUrl[] =
+ "http://metadata/computeMetadata/v1/instance/service-accounts/default/"
+ "token";
+
+// The authentication token scope to request.
+constexpr char kOAuthScope[] = "https://www.googleapis.com/auth/cloud-platform";
+
+/// Returns whether the given path points to a readable file.
+bool IsFile(const string& filename) {
+ std::ifstream fstream(filename.c_str());
+ return fstream.good();
+}
+
+/// Returns the credentials file name from the env variable.
+Status GetEnvironmentVariableFileName(string* filename) {
+ if (!filename) {
+ return errors::FailedPrecondition("'filename' cannot be nullptr.");
+ }
+ const char* result = std::getenv(kGoogleApplicationCredentials);
+ if (!result || !IsFile(result)) {
+ return errors::NotFound(strings::StrCat("$", kGoogleApplicationCredentials,
+ " is not set or corrupt."));
+ }
+ *filename = result;
+ return Status::OK();
+}
+
+/// Returns the well known file produced by command 'gcloud auth login'.
+Status GetWellKnownFileName(string* filename) {
+ if (!filename) {
+ return errors::FailedPrecondition("'filename' cannot be nullptr.");
+ }
+ string config_dir;
+ const char* config_dir_override = std::getenv(kCloudSdkConfig);
+ if (config_dir_override) {
+ config_dir = config_dir_override;
+ } else {
+ // Determine the home dir path.
+ const char* home_dir = std::getenv("HOME");
+ if (!home_dir) {
+ return errors::FailedPrecondition("Could not read $HOME.");
+ }
+ config_dir = io::JoinPath(home_dir, kGCloudConfigFolder);
+ }
+ auto result = io::JoinPath(config_dir, kWellKnownCredentialsFile);
+ if (!IsFile(result)) {
+ return errors::NotFound(
+ "Could not find the credentials file in the standard gcloud location.");
+ }
+ *filename = result;
+ return Status::OK();
+}
+
+} // namespace
+
+GoogleAuthProvider::GoogleAuthProvider()
+ : GoogleAuthProvider(
+ std::unique_ptr<OAuthClient>(new OAuthClient()),
+ std::unique_ptr<HttpRequest::Factory>(new HttpRequest::Factory()),
+ Env::Default()) {}
+
+GoogleAuthProvider::GoogleAuthProvider(
+ std::unique_ptr<OAuthClient> oauth_client,
+ std::unique_ptr<HttpRequest::Factory> http_request_factory, Env* env)
+ : oauth_client_(std::move(oauth_client)),
+ http_request_factory_(std::move(http_request_factory)),
+ env_(env) {}
+
+Status GoogleAuthProvider::GetToken(string* t) {
+ const uint64 now_sec = env_->NowSeconds();
+
+ if (!current_token_.empty() &&
+ now_sec + kExpirationTimeMarginSec < expiration_timestamp_sec_) {
+ *t = current_token_;
+ return Status::OK();
+ }
+ if (GetTokenFromFiles().ok() || GetTokenFromGce().ok()) {
+ *t = current_token_;
+ return Status::OK();
+ }
+ return errors::FailedPrecondition(
+ "All attempts to get a Google authentication bearer token failed.");
+}
+
+Status GoogleAuthProvider::GetTokenFromFiles() {
+ string credentials_filename;
+ if (!GetEnvironmentVariableFileName(&credentials_filename).ok() &&
+ !GetWellKnownFileName(&credentials_filename).ok()) {
+ return errors::NotFound("Could not locate the credentials file.");
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ std::ifstream credentials_fstream(credentials_filename);
+ if (!reader.parse(credentials_fstream, json)) {
+ return errors::FailedPrecondition(
+ "Couldn't parse the JSON credentials file.");
+ }
+ if (json.isMember("refresh_token")) {
+ TF_RETURN_IF_ERROR(oauth_client_->GetTokenFromRefreshTokenJson(
+ json, kOAuthV3Url, &current_token_, &expiration_timestamp_sec_));
+ } else if (json.isMember("private_key")) {
+ TF_RETURN_IF_ERROR(oauth_client_->GetTokenFromServiceAccountJson(
+ json, kOAuthV4Url, kOAuthScope, &current_token_,
+ &expiration_timestamp_sec_));
+ } else {
+ return errors::FailedPrecondition(
+ "Unexpected content of the JSON credentials file.");
+ }
+ return Status::OK();
+}
+
+Status GoogleAuthProvider::GetTokenFromGce() {
+ std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
+ std::unique_ptr<char[]> response_buffer(
+ new char[OAuthClient::kResponseBufferSize]);
+ const uint64 request_timestamp_sec = env_->NowSeconds();
+ StringPiece response;
+ TF_RETURN_IF_ERROR(request->Init());
+ TF_RETURN_IF_ERROR(request->SetUri(kGceTokenUrl));
+ TF_RETURN_IF_ERROR(request->AddHeader("Metadata-Flavor", "Google"));
+ TF_RETURN_IF_ERROR(request->SetResultBuffer(
+ response_buffer.get(), OAuthClient::kResponseBufferSize, &response));
+ TF_RETURN_IF_ERROR(request->Send());
+
+ TF_RETURN_IF_ERROR(oauth_client_->ParseOAuthResponse(
+ response, request_timestamp_sec, &current_token_,
+ &expiration_timestamp_sec_));
+ return Status::OK();
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/google_auth_provider.h b/tensorflow/core/platform/cloud/google_auth_provider.h
new file mode 100644
index 0000000000..0a1f755c8d
--- /dev/null
+++ b/tensorflow/core/platform/cloud/google_auth_provider.h
@@ -0,0 +1,57 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_GOOGLE_AUTH_PROVIDER_H_
+#define TENSORFLOW_CORE_PLATFORM_GOOGLE_AUTH_PROVIDER_H_
+
+#include <memory>
+#include "tensorflow/core/platform/cloud/auth_provider.h"
+#include "tensorflow/core/platform/cloud/oauth_client.h"
+
+namespace tensorflow {
+
+/// Implementation based on Google Application Default Credentials.
+class GoogleAuthProvider : public AuthProvider {
+ public:
+ GoogleAuthProvider();
+ explicit GoogleAuthProvider(
+ std::unique_ptr<OAuthClient> oauth_client,
+ std::unique_ptr<HttpRequest::Factory> http_request_factory, Env* env);
+ virtual ~GoogleAuthProvider() {}
+
+ /// Returns the short-term authentication bearer token.
+ Status GetToken(string* token) override;
+
+ private:
+ /// \brief Gets the bearer token from files.
+ ///
+ /// Tries the file from $GOOGLE_APPLICATION_CREDENTIALS and the
+ /// standard gcloud tool's location.
+ Status GetTokenFromFiles();
+
+ /// Gets the bearer token from Google Compute Engine environment.
+ Status GetTokenFromGce();
+
+ std::unique_ptr<OAuthClient> oauth_client_;
+ std::unique_ptr<HttpRequest::Factory> http_request_factory_;
+ Env* env_;
+ string current_token_;
+ uint64 expiration_timestamp_sec_ = 0;
+ TF_DISALLOW_COPY_AND_ASSIGN(GoogleAuthProvider);
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_GOOGLE_AUTH_PROVIDER_H_
diff --git a/tensorflow/core/platform/cloud/google_auth_provider_test.cc b/tensorflow/core/platform/cloud/google_auth_provider_test.cc
new file mode 100644
index 0000000000..54a6030b74
--- /dev/null
+++ b/tensorflow/core/platform/cloud/google_auth_provider_test.cc
@@ -0,0 +1,200 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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 "tensorflow/core/platform/cloud/google_auth_provider.h"
+#include <stdlib.h>
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/lib/io/path.h"
+#include "tensorflow/core/platform/cloud/http_request_fake.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+
+namespace {
+
+constexpr char kTestData[] = "core/platform/cloud/testdata/";
+
+class FakeEnv : public EnvWrapper {
+ public:
+ FakeEnv() : EnvWrapper(Env::Default()) {}
+
+ uint64 NowSeconds() override { return now; }
+ uint64 now = 10000;
+};
+
+class FakeOAuthClient : public OAuthClient {
+ public:
+ Status GetTokenFromServiceAccountJson(
+ Json::Value json, StringPiece oauth_server_uri, StringPiece scope,
+ string* token, uint64* expiration_timestamp_sec) override {
+ provided_credentials_json = json;
+ *token = return_token;
+ *expiration_timestamp_sec = return_expiration_timestamp;
+ return Status::OK();
+ }
+
+ /// Retrieves a bearer token using a refresh token.
+ Status GetTokenFromRefreshTokenJson(
+ Json::Value json, StringPiece oauth_server_uri, string* token,
+ uint64* expiration_timestamp_sec) override {
+ provided_credentials_json = json;
+ *token = return_token;
+ *expiration_timestamp_sec = return_expiration_timestamp;
+ return Status::OK();
+ }
+
+ string return_token;
+ uint64 return_expiration_timestamp;
+ Json::Value provided_credentials_json;
+};
+
+} // namespace
+
+TEST(GoogleAuthProvider, EnvironmentVariable_Caching) {
+ setenv("GOOGLE_APPLICATION_CREDENTIALS",
+ io::JoinPath(
+ io::JoinPath(testing::TensorFlowSrcRoot(), kTestData).c_str(),
+ "service_account_credentials.json")
+ .c_str(),
+ 1);
+ setenv("CLOUDSDK_CONFIG",
+ io::JoinPath(testing::TensorFlowSrcRoot(), kTestData).c_str(),
+ 1); // Will not be used.
+
+ auto oauth_client = new FakeOAuthClient;
+ std::vector<HttpRequest*> requests;
+
+ FakeEnv env;
+ GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
+ std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ &env);
+ oauth_client->return_token = "fake-token";
+ oauth_client->return_expiration_timestamp = env.NowSeconds() + 3600;
+
+ string token;
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("fake-token", token);
+ EXPECT_EQ("fake_key_id",
+ oauth_client->provided_credentials_json.get("private_key_id", "")
+ .asString());
+
+ // Check that the token is re-used if not expired.
+ oauth_client->return_token = "new-fake-token";
+ env.now += 3000;
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("fake-token", token);
+
+ // Check that the token is re-generated when almost expired.
+ env.now += 598; // 2 seconds before expiration
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("new-fake-token", token);
+}
+
+TEST(GoogleAuthProvider, GCloudRefreshToken) {
+ setenv("GOOGLE_APPLICATION_CREDENTIALS", "", 1);
+ setenv("CLOUDSDK_CONFIG",
+ io::JoinPath(testing::TensorFlowSrcRoot(), kTestData).c_str(), 1);
+
+ auto oauth_client = new FakeOAuthClient;
+ std::vector<HttpRequest*> requests;
+
+ FakeEnv env;
+ GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
+ std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ &env);
+ oauth_client->return_token = "fake-token";
+ oauth_client->return_expiration_timestamp = env.NowSeconds() + 3600;
+
+ string token;
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("fake-token", token);
+ EXPECT_EQ("fake-refresh-token",
+ oauth_client->provided_credentials_json.get("refresh_token", "")
+ .asString());
+}
+
+TEST(GoogleAuthProvider, RunningOnGCE) {
+ setenv("GOOGLE_APPLICATION_CREDENTIALS", "", 1);
+ setenv("CLOUDSDK_CONFIG", "", 1);
+
+ auto oauth_client = new FakeOAuthClient;
+ std::vector<HttpRequest*> requests(
+ {new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/service-accounts"
+ "/default/token\n"
+ "Header Metadata-Flavor: Google\n",
+ R"(
+ {
+ "access_token":"fake-gce-token",
+ "expires_in": 3920,
+ "token_type":"Bearer"
+ })"),
+ new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/service-accounts"
+ "/default/token\n"
+ "Header Metadata-Flavor: Google\n",
+ R"(
+ {
+ "access_token":"new-fake-gce-token",
+ "expires_in": 3920,
+ "token_type":"Bearer"
+ })")});
+
+ FakeEnv env;
+ GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
+ std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ &env);
+
+ string token;
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("fake-gce-token", token);
+
+ // Check that the token is re-used if not expired.
+ env.now += 3700;
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("fake-gce-token", token);
+
+ // Check that the token is re-generated when almost expired.
+ env.now += 598; // 2 seconds before expiration
+ TF_EXPECT_OK(provider.GetToken(&token));
+ EXPECT_EQ("new-fake-gce-token", token);
+}
+
+TEST(GoogleAuthProvider, NothingAvailable) {
+ setenv("GOOGLE_APPLICATION_CREDENTIALS", "", 1);
+ setenv("CLOUDSDK_CONFIG", "", 1);
+
+ auto oauth_client = new FakeOAuthClient;
+
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/service-accounts"
+ "/default/token\n"
+ "Header Metadata-Flavor: Google\n",
+ "", errors::NotFound("404"))});
+
+ FakeEnv env;
+ GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
+ std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ &env);
+
+ string token;
+ EXPECT_FALSE(provider.GetToken(&token).ok());
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/http_request.cc b/tensorflow/core/platform/cloud/http_request.cc
index 38f132e723..5e9d96ba86 100644
--- a/tensorflow/core/platform/cloud/http_request.cc
+++ b/tensorflow/core/platform/cloud/http_request.cc
@@ -24,6 +24,7 @@ limitations under the License.
#include "tensorflow/core/lib/strings/scanner.h"
#include "tensorflow/core/platform/protobuf.h"
#include "tensorflow/core/platform/types.h"
+#include "tensorflow/core/public/version.h"
namespace tensorflow {
@@ -200,8 +201,11 @@ HttpRequest::~HttpRequest() {
}
Status HttpRequest::Init() {
+ if (is_initialized_) {
+ return errors::FailedPrecondition("Already initialized.");
+ }
if (!libcurl_) {
- return errors::Internal("libcurl proxy cannot be nullptr.");
+ return errors::FailedPrecondition("libcurl proxy cannot be nullptr.");
}
TF_RETURN_IF_ERROR(libcurl_->MaybeLoadDll());
curl_ = libcurl_->curl_easy_init();
@@ -211,6 +215,9 @@ Status HttpRequest::Init() {
libcurl_->curl_easy_setopt(curl_, CURLOPT_VERBOSE, kVerboseOutput);
libcurl_->curl_easy_setopt(curl_, CURLOPT_CAPATH, kCertsPath);
+ libcurl_->curl_easy_setopt(
+ curl_, CURLOPT_USERAGENT,
+ strings::StrCat("TensorFlow/", TF_VERSION_STRING).c_str());
// If response buffer is not set, libcurl will print results to stdout,
// so we always set it.
@@ -240,13 +247,19 @@ Status HttpRequest::SetRange(uint64 start, uint64 end) {
return Status::OK();
}
+Status HttpRequest::AddHeader(const string& name, const string& value) {
+ TF_RETURN_IF_ERROR(CheckInitialized());
+ TF_RETURN_IF_ERROR(CheckNotSent());
+ curl_headers_ = libcurl_->curl_slist_append(
+ curl_headers_, strings::StrCat(name, ": ", value).c_str());
+ return Status::OK();
+}
+
Status HttpRequest::AddAuthBearerHeader(const string& auth_token) {
TF_RETURN_IF_ERROR(CheckInitialized());
TF_RETURN_IF_ERROR(CheckNotSent());
if (!auth_token.empty()) {
- curl_headers_ = libcurl_->curl_slist_append(
- curl_headers_,
- strings::StrCat("Authorization: Bearer ", auth_token).c_str());
+ return AddHeader("Authorization", strings::StrCat("Bearer ", auth_token));
}
return Status::OK();
}
@@ -285,6 +298,22 @@ Status HttpRequest::SetPostRequest(const string& body_filepath) {
return Status::OK();
}
+Status HttpRequest::SetPostRequest(const char* buffer, size_t size) {
+ TF_RETURN_IF_ERROR(CheckInitialized());
+ TF_RETURN_IF_ERROR(CheckNotSent());
+ TF_RETURN_IF_ERROR(CheckMethodNotSet());
+ is_method_set_ = true;
+ curl_headers_ = libcurl_->curl_slist_append(
+ curl_headers_, strings::StrCat("Content-Length: ", size).c_str());
+ libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1);
+ libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
+ reinterpret_cast<void*>(this));
+ libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
+ &HttpRequest::ReadCallback);
+ post_body_buffer_ = StringPiece(buffer, size);
+ return Status::OK();
+}
+
Status HttpRequest::SetPostRequest() {
TF_RETURN_IF_ERROR(CheckInitialized());
TF_RETURN_IF_ERROR(CheckNotSent());
@@ -337,6 +366,19 @@ size_t HttpRequest::WriteCallback(const void* ptr, size_t size, size_t nmemb,
return bytes_to_copy;
}
+size_t HttpRequest::ReadCallback(void* ptr, size_t size, size_t nmemb,
+ FILE* this_object) {
+ CHECK(ptr);
+ auto that = reinterpret_cast<HttpRequest*>(this_object);
+ CHECK(that->post_body_read_ <= that->post_body_buffer_.size());
+ const size_t bytes_to_copy = std::min(
+ size * nmemb, that->post_body_buffer_.size() - that->post_body_read_);
+ memcpy(ptr, that->post_body_buffer_.data() + that->post_body_read_,
+ bytes_to_copy);
+ that->post_body_read_ += bytes_to_copy;
+ return bytes_to_copy;
+}
+
Status HttpRequest::Send() {
TF_RETURN_IF_ERROR(CheckInitialized());
TF_RETURN_IF_ERROR(CheckNotSent());
diff --git a/tensorflow/core/platform/cloud/http_request.h b/tensorflow/core/platform/cloud/http_request.h
index 19aed67e6a..2e0f92bc13 100644
--- a/tensorflow/core/platform/cloud/http_request.h
+++ b/tensorflow/core/platform/cloud/http_request.h
@@ -16,7 +16,6 @@ limitations under the License.
#ifndef TENSORFLOW_CORE_PLATFORM_HTTP_REQUEST_H_
#define TENSORFLOW_CORE_PLATFORM_HTTP_REQUEST_H_
-#include <functional>
#include <string>
#include <vector>
#include <curl/curl.h>
@@ -64,6 +63,9 @@ class HttpRequest {
/// (note that the right border is included).
virtual Status SetRange(uint64 start, uint64 end);
+ /// Sets a request header.
+ virtual Status AddHeader(const string& name, const string& value);
+
/// Sets the 'Authorization' header to the value of 'Bearer ' + auth_token.
virtual Status AddAuthBearerHeader(const string& auth_token);
@@ -75,6 +77,11 @@ class HttpRequest {
/// The request body will be taken from the specified file.
virtual Status SetPostRequest(const string& body_filepath);
+ /// \brief Makes the request a POST request.
+ ///
+ /// The request body will be taken from the specified buffer.
+ virtual Status SetPostRequest(const char* buffer, size_t size);
+
/// Makes the request a POST request.
virtual Status SetPostRequest();
@@ -91,15 +98,23 @@ class HttpRequest {
virtual Status Send();
private:
- /// A callback in the form which can be accepted by libcurl.
+ /// A write callback in the form which can be accepted by libcurl.
static size_t WriteCallback(const void* ptr, size_t size, size_t nmemb,
void* userdata);
+ /// A read callback in the form which can be accepted by libcurl.
+ static size_t ReadCallback(void* ptr, size_t size, size_t nmemb,
+ FILE* userdata);
Status CheckInitialized() const;
Status CheckMethodNotSet() const;
Status CheckNotSent() const;
std::unique_ptr<LibCurl> libcurl_;
+
FILE* post_body_ = nullptr;
+
+ StringPiece post_body_buffer_;
+ size_t post_body_read_ = 0;
+
char* response_buffer_ = nullptr;
size_t response_buffer_size_ = 0;
size_t response_buffer_written_ = 0;
diff --git a/tensorflow/core/platform/cloud/http_request_fake.h b/tensorflow/core/platform/cloud/http_request_fake.h
new file mode 100644
index 0000000000..b091e5d630
--- /dev/null
+++ b/tensorflow/core/platform/cloud/http_request_fake.h
@@ -0,0 +1,166 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_HTTP_REQUEST_FAKE_H_
+#define TENSORFLOW_CORE_PLATFORM_HTTP_REQUEST_FAKE_H_
+
+#include <fstream>
+#include <string>
+#include <vector>
+#include <curl/curl.h>
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/platform/cloud/http_request.h"
+#include "tensorflow/core/platform/macros.h"
+#include "tensorflow/core/platform/protobuf.h"
+#include "tensorflow/core/platform/test.h"
+#include "tensorflow/core/platform/types.h"
+
+namespace tensorflow {
+
+/// Fake HttpRequest for testing.
+class FakeHttpRequest : public HttpRequest {
+ public:
+ /// Return the response for the given request.
+ FakeHttpRequest(const string& request, const string& response)
+ : FakeHttpRequest(request, response, Status::OK(), nullptr) {}
+
+ /// \brief Return the response for the request and capture the POST body.
+ ///
+ /// Post body is not expected to be a part of the 'request' parameter.
+ FakeHttpRequest(const string& request, const string& response,
+ string* captured_post_body)
+ : FakeHttpRequest(request, response, Status::OK(), captured_post_body) {}
+
+ /// \brief Return the response and the status for the given request.
+ FakeHttpRequest(const string& request, const string& response,
+ Status response_status)
+ : FakeHttpRequest(request, response, response_status, nullptr) {}
+
+ /// \brief Return the response and the status for the given request
+ /// and capture the POST body.
+ ///
+ /// Post body is not expected to be a part of the 'request' parameter.
+ FakeHttpRequest(const string& request, const string& response,
+ Status response_status, string* captured_post_body)
+ : expected_request_(request),
+ response_(response),
+ response_status_(response_status),
+ captured_post_body_(captured_post_body) {}
+
+ Status Init() override { return Status::OK(); }
+ Status SetUri(const string& uri) override {
+ actual_request_ += "Uri: " + uri + "\n";
+ return Status::OK();
+ }
+ Status SetRange(uint64 start, uint64 end) override {
+ actual_request_ += strings::StrCat("Range: ", start, "-", end, "\n");
+ return Status::OK();
+ }
+ Status AddHeader(const string& name, const string& value) override {
+ actual_request_ += "Header " + name + ": " + value + "\n";
+ return Status::OK();
+ }
+ Status AddAuthBearerHeader(const string& auth_token) override {
+ actual_request_ += "Auth Token: " + auth_token + "\n";
+ return Status::OK();
+ }
+ Status SetDeleteRequest() override {
+ actual_request_ += "Delete: yes\n";
+ return Status::OK();
+ }
+ Status SetPostRequest(const string& body_filepath) override {
+ std::ifstream stream(body_filepath);
+ string content((std::istreambuf_iterator<char>(stream)),
+ std::istreambuf_iterator<char>());
+ if (captured_post_body_) {
+ *captured_post_body_ = content;
+ } else {
+ actual_request_ += "Post body: " + content + "\n";
+ }
+ return Status::OK();
+ }
+ Status SetPostRequest(const char* buffer, size_t size) override {
+ if (captured_post_body_) {
+ *captured_post_body_ = string(buffer, size);
+ } else {
+ actual_request_ +=
+ strings::StrCat("Post body: ", StringPiece(buffer, size), "\n");
+ }
+ return Status::OK();
+ }
+ Status SetPostRequest() override {
+ if (captured_post_body_) {
+ *captured_post_body_ = "<empty>";
+ } else {
+ actual_request_ += "Post: yes\n";
+ }
+ return Status::OK();
+ }
+ Status SetResultBuffer(char* scratch, size_t size,
+ StringPiece* result) override {
+ scratch_ = scratch;
+ size_ = size;
+ result_ = result;
+ return Status::OK();
+ }
+ Status Send() override {
+ EXPECT_EQ(expected_request_, actual_request_) << "Unexpected HTTP request.";
+ if (scratch_ && result_) {
+ auto actual_size = std::min(response_.size(), size_);
+ memcpy(scratch_, response_.c_str(), actual_size);
+ *result_ = StringPiece(scratch_, actual_size);
+ }
+ return response_status_;
+ }
+
+ private:
+ char* scratch_ = nullptr;
+ size_t size_ = 0;
+ StringPiece* result_ = nullptr;
+ string expected_request_;
+ string actual_request_;
+ string response_;
+ Status response_status_;
+ string* captured_post_body_ = nullptr;
+};
+
+/// Fake HttpRequest factory for testing.
+class FakeHttpRequestFactory : public HttpRequest::Factory {
+ public:
+ FakeHttpRequestFactory(const std::vector<HttpRequest*>* requests)
+ : requests_(requests) {}
+
+ ~FakeHttpRequestFactory() {
+ EXPECT_EQ(current_index_, requests_->size())
+ << "Not all expected requests were made.";
+ }
+
+ HttpRequest* Create() override {
+ EXPECT_LT(current_index_, requests_->size())
+ << "Too many calls of HttpRequest factory.";
+ return (*requests_)[current_index_++];
+ }
+
+ private:
+ const std::vector<HttpRequest*>* requests_;
+ int current_index_ = 0;
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_HTTP_REQUEST_FAKE_H_
diff --git a/tensorflow/core/platform/cloud/http_request_test.cc b/tensorflow/core/platform/cloud/http_request_test.cc
index 247514c9da..e7bfcd4cb4 100644
--- a/tensorflow/core/platform/cloud/http_request_test.cc
+++ b/tensorflow/core/platform/cloud/http_request_test.cc
@@ -81,7 +81,7 @@ class FakeLibCurl : public LibCurl {
CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
size_t (*param)(void*, size_t, size_t,
FILE*)) override {
- EXPECT_EQ(param, &fread) << "Expected the standard fread() function.";
+ read_callback = param;
return CURLE_OK;
}
CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
@@ -98,11 +98,11 @@ class FakeLibCurl : public LibCurl {
}
CURLcode curl_easy_perform(CURL* curl) override {
if (read_data) {
- char buffer[100];
+ char buffer[3];
int bytes_read;
posted_content = "";
do {
- bytes_read = fread(buffer, 1, 100, read_data);
+ bytes_read = read_callback(buffer, 1, sizeof(buffer), read_data);
posted_content =
strings::StrCat(posted_content, StringPiece(buffer, bytes_read));
} while (bytes_read > 0);
@@ -158,11 +158,13 @@ class FakeLibCurl : public LibCurl {
bool is_initialized = false;
bool is_cleaned_up = false;
std::vector<string>* headers = nullptr;
- FILE* read_data = nullptr;
bool is_post = false;
void* write_data = nullptr;
size_t (*write_callback)(const void* ptr, size_t size, size_t nmemb,
void* userdata) = nullptr;
+ FILE* read_data = nullptr;
+ size_t (*read_callback)(void* ptr, size_t size, size_t nmemb,
+ FILE* userdata) = &fread;
// Outcome of performing the request.
string posted_content;
};
@@ -193,7 +195,7 @@ TEST(HttpRequestTest, GetRequest) {
EXPECT_FALSE(libcurl->is_post);
}
-TEST(HttpRequestTest, PostRequest_WithBody) {
+TEST(HttpRequestTest, PostRequest_WithBody_FromFile) {
FakeLibCurl* libcurl = new FakeLibCurl("", 200);
HttpRequest http_request((std::unique_ptr<LibCurl>(libcurl)));
TF_EXPECT_OK(http_request.Init());
@@ -221,6 +223,29 @@ TEST(HttpRequestTest, PostRequest_WithBody) {
std::remove(content_filename.c_str());
}
+TEST(HttpRequestTest, PostRequest_WithBody_FromMemory) {
+ FakeLibCurl* libcurl = new FakeLibCurl("", 200);
+ HttpRequest http_request((std::unique_ptr<LibCurl>(libcurl)));
+ TF_EXPECT_OK(http_request.Init());
+
+ string content = "post body content";
+
+ TF_EXPECT_OK(http_request.SetUri("http://www.testuri.com"));
+ TF_EXPECT_OK(http_request.AddAuthBearerHeader("fake-bearer"));
+ TF_EXPECT_OK(http_request.SetPostRequest(content.c_str(), content.size()));
+ TF_EXPECT_OK(http_request.Send());
+
+ // Check interactions with libcurl.
+ EXPECT_TRUE(libcurl->is_initialized);
+ EXPECT_EQ("http://www.testuri.com", libcurl->url);
+ EXPECT_EQ("", libcurl->custom_request);
+ EXPECT_EQ(2, libcurl->headers->size());
+ EXPECT_EQ("Authorization: Bearer fake-bearer", (*libcurl->headers)[0]);
+ EXPECT_EQ("Content-Length: 17", (*libcurl->headers)[1]);
+ EXPECT_TRUE(libcurl->is_post);
+ EXPECT_EQ("post body content", libcurl->posted_content);
+}
+
TEST(HttpRequestTest, PostRequest_WithoutBody) {
FakeLibCurl* libcurl = new FakeLibCurl("", 200);
HttpRequest http_request((std::unique_ptr<LibCurl>(libcurl)));
diff --git a/tensorflow/core/platform/cloud/oauth_client.cc b/tensorflow/core/platform/cloud/oauth_client.cc
new file mode 100644
index 0000000000..629ac11f8d
--- /dev/null
+++ b/tensorflow/core/platform/cloud/oauth_client.cc
@@ -0,0 +1,288 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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 "tensorflow/core/platform/cloud/oauth_client.h"
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fstream>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/platform/cloud/base64.h"
+#include "tensorflow/core/platform/cloud/http_request.h"
+#include "tensorflow/core/platform/env.h"
+
+namespace tensorflow {
+
+namespace {
+
+// The requested lifetime of a auth bearer token.
+constexpr int kRequestedTokenLifetimeSec = 3600;
+
+// The crypto algorithm to be used with OAuth.
+constexpr char kCryptoAlgorithm[] = "RS256";
+
+// The token type for the OAuth request.
+constexpr char kJwtType[] = "JWT";
+
+// The grant type for the OAuth request. Already URL-encoded for convenience.
+constexpr char kGrantType[] =
+ "urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer";
+
+Status ReadJsonValue(Json::Value json, const string& name, Json::Value* value) {
+ if (!value) {
+ return errors::FailedPrecondition("'value' cannot be nullptr.");
+ }
+ *value = json.get(name, Json::Value::null);
+ if (*value == Json::Value::null) {
+ return errors::FailedPrecondition(
+ strings::StrCat("Couldn't read a JSON value '", name, "'."));
+ }
+ return Status::OK();
+}
+
+Status ReadJsonString(Json::Value json, const string& name, string* value) {
+ Json::Value json_value;
+ TF_RETURN_IF_ERROR(ReadJsonValue(json, name, &json_value));
+ if (!json_value.isString()) {
+ return errors::FailedPrecondition(
+ strings::StrCat("JSON value '", name, "' is not string."));
+ }
+ *value = json_value.asString();
+ return Status::OK();
+}
+
+Status ReadJsonInt(Json::Value json, const string& name, int64* value) {
+ Json::Value json_value;
+ TF_RETURN_IF_ERROR(ReadJsonValue(json, name, &json_value));
+ if (!json_value.isIntegral()) {
+ return errors::FailedPrecondition(
+ strings::StrCat("JSON value '", name, "' is not integer."));
+ }
+ *value = json_value.asInt64();
+ return Status::OK();
+}
+
+Status CreateSignature(RSA* private_key, StringPiece to_sign,
+ string* signature) {
+ if (!private_key || !signature) {
+ return errors::FailedPrecondition(
+ "'private_key' and 'signature' cannot be nullptr.");
+ }
+
+ const auto md = EVP_sha256();
+ if (!md) {
+ return errors::Internal("Could not get a sha256 encryptor.");
+ }
+ std::unique_ptr<EVP_MD_CTX, std::function<void(EVP_MD_CTX*)>> md_ctx(
+ EVP_MD_CTX_create(), [](EVP_MD_CTX* ptr) { EVP_MD_CTX_destroy(ptr); });
+ if (!md_ctx.get()) {
+ return errors::Internal("Could not create MD_CTX.");
+ }
+
+ std::unique_ptr<EVP_PKEY, std::function<void(EVP_PKEY*)>> key(
+ EVP_PKEY_new(), [](EVP_PKEY* ptr) { EVP_PKEY_free(ptr); });
+ EVP_PKEY_set1_RSA(key.get(), private_key);
+
+ if (EVP_DigestSignInit(md_ctx.get(), NULL, md, NULL, key.get()) != 1) {
+ return errors::Internal("DigestInit failed.");
+ }
+ if (EVP_DigestSignUpdate(md_ctx.get(), to_sign.data(), to_sign.size()) != 1) {
+ return errors::Internal("DigestUpdate failed.");
+ }
+ size_t sig_len = 0;
+ if (EVP_DigestSignFinal(md_ctx.get(), NULL, &sig_len) != 1) {
+ return errors::Internal("DigestFinal (get signature length) failed.");
+ }
+ std::unique_ptr<unsigned char[]> sig(new unsigned char[sig_len]);
+ if (EVP_DigestSignFinal(md_ctx.get(), sig.get(), &sig_len) != 1) {
+ return errors::Internal("DigestFinal (signature compute) failed.");
+ }
+ EVP_MD_CTX_cleanup(md_ctx.get());
+ return Base64Encode(StringPiece(reinterpret_cast<char*>(sig.get()), sig_len),
+ signature);
+}
+
+/// Encodes a claim for a JSON web token (JWT) to make an OAuth request.
+Status EncodeJwtClaim(StringPiece client_email, StringPiece scope,
+ StringPiece audience, uint64 request_timestamp_sec,
+ string* encoded) {
+ // Step 1: create the JSON with the claim.
+ Json::Value root;
+ root["iss"] = Json::Value(client_email.begin(), client_email.end());
+ root["scope"] = Json::Value(scope.begin(), scope.end());
+ root["aud"] = Json::Value(audience.begin(), audience.end());
+
+ const auto expiration_timestamp_sec =
+ request_timestamp_sec + kRequestedTokenLifetimeSec;
+
+ root["iat"] = request_timestamp_sec;
+ root["exp"] = expiration_timestamp_sec;
+
+ // Step 2: represent the JSON as a string.
+ string claim = root.toStyledString();
+
+ // Step 3: encode the string as base64.
+ return Base64Encode(claim, encoded);
+}
+
+/// Encodes a header for a JSON web token (JWT) to make an OAuth request.
+Status EncodeJwtHeader(StringPiece key_id, string* encoded) {
+ // Step 1: create the JSON with the header.
+ Json::Value root;
+ root["alg"] = kCryptoAlgorithm;
+ root["typ"] = kJwtType;
+ root["kid"] = Json::Value(key_id.begin(), key_id.end());
+
+ // Step 2: represent the JSON as a string.
+ const string header = root.toStyledString();
+
+ // Step 3: encode the string as base64.
+ return Base64Encode(header, encoded);
+}
+
+} // namespace
+
+OAuthClient::OAuthClient()
+ : OAuthClient(
+ std::unique_ptr<HttpRequest::Factory>(new HttpRequest::Factory()),
+ Env::Default()) {}
+
+OAuthClient::OAuthClient(
+ std::unique_ptr<HttpRequest::Factory> http_request_factory, Env* env)
+ : http_request_factory_(std::move(http_request_factory)), env_(env) {}
+
+Status OAuthClient::GetTokenFromServiceAccountJson(
+ Json::Value json, StringPiece oauth_server_uri, StringPiece scope,
+ string* token, uint64* expiration_timestamp_sec) {
+ if (!token || !expiration_timestamp_sec) {
+ return errors::FailedPrecondition(
+ "'token' and 'expiration_timestamp_sec' cannot be nullptr.");
+ }
+ string private_key_serialized, private_key_id, client_id, client_email;
+ TF_RETURN_IF_ERROR(
+ ReadJsonString(json, "private_key", &private_key_serialized));
+ TF_RETURN_IF_ERROR(ReadJsonString(json, "private_key_id", &private_key_id));
+ TF_RETURN_IF_ERROR(ReadJsonString(json, "client_id", &client_id));
+ TF_RETURN_IF_ERROR(ReadJsonString(json, "client_email", &client_email));
+
+ std::unique_ptr<BIO, std::function<void(BIO*)>> bio(
+ BIO_new(BIO_s_mem()), [](BIO* ptr) { BIO_free_all(ptr); });
+ if (BIO_puts(bio.get(), private_key_serialized.c_str()) !=
+ static_cast<int>(private_key_serialized.size())) {
+ return errors::Internal("Could not load the private key.");
+ }
+ std::unique_ptr<RSA, std::function<void(RSA*)>> private_key(
+ PEM_read_bio_RSAPrivateKey(bio.get(), nullptr, nullptr, nullptr),
+ [](RSA* ptr) { RSA_free(ptr); });
+ if (!private_key.get()) {
+ return errors::Internal("Could not deserialize the private key.");
+ }
+
+ const uint64 request_timestamp_sec = env_->NowSeconds();
+
+ string encoded_claim, encoded_header;
+ TF_RETURN_IF_ERROR(EncodeJwtHeader(private_key_id, &encoded_header));
+ TF_RETURN_IF_ERROR(EncodeJwtClaim(client_email, scope, oauth_server_uri,
+ request_timestamp_sec, &encoded_claim));
+ const string to_sign = encoded_header + "." + encoded_claim;
+ string signature;
+ TF_RETURN_IF_ERROR(CreateSignature(private_key.get(), to_sign, &signature));
+ const string jwt = to_sign + "." + signature;
+ const string request_body =
+ strings::StrCat("grant_type=", kGrantType, "&assertion=", jwt);
+
+ // Send the request to the Google OAuth 2.0 server to get the token.
+ std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
+ std::unique_ptr<char[]> response_buffer(new char[kResponseBufferSize]);
+ StringPiece response;
+ TF_RETURN_IF_ERROR(request->Init());
+ TF_RETURN_IF_ERROR(request->SetUri(oauth_server_uri.ToString()));
+ TF_RETURN_IF_ERROR(
+ request->SetPostRequest(request_body.c_str(), request_body.size()));
+ TF_RETURN_IF_ERROR(request->SetResultBuffer(response_buffer.get(),
+ kResponseBufferSize, &response));
+ TF_RETURN_IF_ERROR(request->Send());
+
+ TF_RETURN_IF_ERROR(ParseOAuthResponse(response, request_timestamp_sec, token,
+ expiration_timestamp_sec));
+ return Status::OK();
+}
+
+Status OAuthClient::GetTokenFromRefreshTokenJson(
+ Json::Value json, StringPiece oauth_server_uri, string* token,
+ uint64* expiration_timestamp_sec) {
+ if (!token || !expiration_timestamp_sec) {
+ return errors::FailedPrecondition(
+ "'token' and 'expiration_timestamp_sec' cannot be nullptr.");
+ }
+ string client_id, client_secret, refresh_token;
+ TF_RETURN_IF_ERROR(ReadJsonString(json, "client_id", &client_id));
+ TF_RETURN_IF_ERROR(ReadJsonString(json, "client_secret", &client_secret));
+ TF_RETURN_IF_ERROR(ReadJsonString(json, "refresh_token", &refresh_token));
+
+ const auto request_body = strings::StrCat(
+ "client_id=", client_id, "&client_secret=", client_secret,
+ "&refresh_token=", refresh_token, "&grant_type=refresh_token");
+
+ const uint64 request_timestamp_sec = env_->NowSeconds();
+
+ std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
+ std::unique_ptr<char[]> response_buffer(new char[kResponseBufferSize]);
+ StringPiece response;
+ TF_RETURN_IF_ERROR(request->Init());
+ TF_RETURN_IF_ERROR(request->SetUri(oauth_server_uri.ToString()));
+ TF_RETURN_IF_ERROR(
+ request->SetPostRequest(request_body.c_str(), request_body.size()));
+ TF_RETURN_IF_ERROR(request->SetResultBuffer(response_buffer.get(),
+ kResponseBufferSize, &response));
+ TF_RETURN_IF_ERROR(request->Send());
+
+ TF_RETURN_IF_ERROR(ParseOAuthResponse(response, request_timestamp_sec, token,
+ expiration_timestamp_sec));
+ return Status::OK();
+}
+
+Status OAuthClient::ParseOAuthResponse(StringPiece response,
+ uint64 request_timestamp_sec,
+ string* token,
+ uint64* expiration_timestamp_sec) {
+ if (!token || !expiration_timestamp_sec) {
+ return errors::FailedPrecondition(
+ "'token' and 'expiration_timestamp_sec' cannot be nullptr.");
+ }
+ Json::Value root;
+ Json::Reader reader;
+ if (!reader.parse(response.begin(), response.end(), root)) {
+ return errors::Internal("Couldn't parse JSON response from OAuth server.");
+ }
+
+ string token_type;
+ TF_RETURN_IF_ERROR(ReadJsonString(root, "token_type", &token_type));
+ if (token_type != "Bearer") {
+ return errors::FailedPrecondition("Unexpected Oauth token type: " +
+ token_type);
+ }
+ int64 expires_in;
+ TF_RETURN_IF_ERROR(ReadJsonInt(root, "expires_in", &expires_in));
+ *expiration_timestamp_sec = request_timestamp_sec + expires_in;
+ TF_RETURN_IF_ERROR(ReadJsonString(root, "access_token", token));
+
+ return Status::OK();
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/oauth_client.h b/tensorflow/core/platform/cloud/oauth_client.h
new file mode 100644
index 0000000000..dadd400cb1
--- /dev/null
+++ b/tensorflow/core/platform/cloud/oauth_client.h
@@ -0,0 +1,65 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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.
+==============================================================================*/
+
+#ifndef THIRD_PARTY_TENSORFLOW_CORE_PLATFORM_CLOUD_OAUTH_CLIENT_H_
+#define THIRD_PARTY_TENSORFLOW_CORE_PLATFORM_CLOUD_OAUTH_CLIENT_H_
+
+#include <memory>
+#include "include/json/json.h"
+#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/platform/cloud/http_request.h"
+#include "tensorflow/core/platform/env.h"
+
+namespace tensorflow {
+
+/// OAuth 2.0 client.
+class OAuthClient {
+ public:
+ OAuthClient();
+ explicit OAuthClient(
+ std::unique_ptr<HttpRequest::Factory> http_request_factory, Env* env);
+ virtual ~OAuthClient() {}
+
+ /// \brief Retrieves a bearer token using a private key.
+ ///
+ /// Retrieves the authentication bearer token using a JSON file
+ /// with the client's private key.
+ virtual Status GetTokenFromServiceAccountJson(
+ Json::Value json, StringPiece oauth_server_uri, StringPiece scope,
+ string* token, uint64* expiration_timestamp_sec);
+
+ /// Retrieves a bearer token using a refresh token.
+ virtual Status GetTokenFromRefreshTokenJson(Json::Value json,
+ StringPiece oauth_server_uri,
+ string* token,
+ uint64* expiration_timestamp_sec);
+
+ /// Parses the JSON response with the token from an OAuth 2.0 server.
+ virtual Status ParseOAuthResponse(StringPiece response,
+ uint64 request_timestamp_sec, string* token,
+ uint64* expiration_timestamp_sec);
+
+ /// The max size of the JSON response from an OAuth 2.0 server, in bytes.
+ static constexpr size_t kResponseBufferSize = 1000;
+
+ private:
+ std::unique_ptr<HttpRequest::Factory> http_request_factory_;
+ Env* env_;
+ TF_DISALLOW_COPY_AND_ASSIGN(OAuthClient);
+};
+
+} // namespace tensorflow
+
+#endif // THIRD_PARTY_TENSORFLOW_CORE_PLATFORM_CLOUD_OAUTH_CLIENT_H_
diff --git a/tensorflow/core/platform/cloud/oauth_client_test.cc b/tensorflow/core/platform/cloud/oauth_client_test.cc
new file mode 100644
index 0000000000..1bfc956c1c
--- /dev/null
+++ b/tensorflow/core/platform/cloud/oauth_client_test.cc
@@ -0,0 +1,203 @@
+/* Copyright 2016 Google Inc. All Rights Reserved.
+
+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 "tensorflow/core/platform/cloud/oauth_client.h"
+#include <fstream>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/lib/io/path.h"
+#include "tensorflow/core/lib/strings/scanner.h"
+#include "tensorflow/core/platform/cloud/base64.h"
+#include "tensorflow/core/platform/cloud/http_request_fake.h"
+#include "tensorflow/core/platform/env.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+namespace {
+
+constexpr char kTestData[] = "core/platform/cloud/testdata/";
+
+constexpr char kTokenJson[] = R"(
+ {
+ "access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
+ "expires_in":3920,
+ "token_type":"Bearer"
+ })";
+
+class FakeEnv : public EnvWrapper {
+ public:
+ FakeEnv() : EnvWrapper(Env::Default()) {}
+
+ uint64 NowSeconds() override { return now; }
+ uint64 now = 10000;
+};
+
+} // namespace
+
+TEST(OAuthClientTest, ParseOAuthResponse) {
+ const uint64 request_timestamp = 100;
+ string token;
+ uint64 expiration_timestamp;
+ TF_EXPECT_OK(OAuthClient().ParseOAuthResponse(kTokenJson, request_timestamp,
+ &token, &expiration_timestamp));
+ EXPECT_EQ("1/fFAGRNJru1FTz70BzhT3Zg", token);
+ EXPECT_EQ(4020, expiration_timestamp);
+}
+
+TEST(OAuthClientTest, GetTokenFromRefreshTokenJson) {
+ const string credentials_json = R"(
+ {
+ "client_id": "test_client_id",
+ "client_secret": "test_client_secret",
+ "refresh_token": "test_refresh_token",
+ "type": "authorized_user"
+ })";
+ Json::Value json;
+ Json::Reader reader;
+ ASSERT_TRUE(reader.parse(credentials_json, json));
+
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: https://www.googleapis.com/oauth2/v3/token\n"
+ "Post body: client_id=test_client_id&"
+ "client_secret=test_client_secret&"
+ "refresh_token=test_refresh_token&grant_type=refresh_token\n",
+ kTokenJson)});
+ FakeEnv env;
+ OAuthClient client(std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ &env);
+ string token;
+ uint64 expiration_timestamp;
+ TF_EXPECT_OK(client.GetTokenFromRefreshTokenJson(
+ json, "https://www.googleapis.com/oauth2/v3/token", &token,
+ &expiration_timestamp));
+ EXPECT_EQ("1/fFAGRNJru1FTz70BzhT3Zg", token);
+ EXPECT_EQ(13920, expiration_timestamp);
+}
+
+TEST(OAuthClientTest, GetTokenFromServiceAccountJson) {
+ std::ifstream credentials(
+ io::JoinPath(io::JoinPath(testing::TensorFlowSrcRoot(), kTestData),
+ "service_account_credentials.json"));
+ ASSERT_TRUE(credentials.is_open());
+ Json::Value json;
+ Json::Reader reader;
+ ASSERT_TRUE(reader.parse(credentials, json));
+
+ string post_body;
+ std::vector<HttpRequest*> requests(
+ {new FakeHttpRequest("Uri: https://www.googleapis.com/oauth2/v3/token\n",
+ kTokenJson, &post_body)});
+ FakeEnv env;
+ OAuthClient client(std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ &env);
+ string token;
+ uint64 expiration_timestamp;
+ TF_EXPECT_OK(client.GetTokenFromServiceAccountJson(
+ json, "https://www.googleapis.com/oauth2/v3/token",
+ "https://test-token-scope.com", &token, &expiration_timestamp));
+ EXPECT_EQ("1/fFAGRNJru1FTz70BzhT3Zg", token);
+ EXPECT_EQ(13920, expiration_timestamp);
+
+ // Now look at the JWT claim that was sent to the OAuth server.
+ StringPiece grant_type, assertion;
+ ASSERT_TRUE(strings::Scanner(post_body)
+ .OneLiteral("grant_type=")
+ .RestartCapture()
+ .ScanEscapedUntil('&')
+ .StopCapture()
+ .OneLiteral("&assertion=")
+ .GetResult(&assertion, &grant_type));
+ EXPECT_EQ("urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer",
+ grant_type.ToString());
+
+ int last_dot = assertion.ToString().find_last_of(".");
+ string header_dot_claim = assertion.ToString().substr(0, last_dot);
+ string signature_encoded = assertion.ToString().substr(last_dot + 1);
+
+ // Check that 'signature' signs 'header_dot_claim'.
+
+ // Read the serialized public key.
+ std::ifstream public_key_stream(
+ io::JoinPath(io::JoinPath(testing::TensorFlowSrcRoot(), kTestData),
+ "service_account_public_key.txt"));
+ string public_key_serialized(
+ (std::istreambuf_iterator<char>(public_key_stream)),
+ (std::istreambuf_iterator<char>()));
+
+ // Deserialize the public key.
+ auto bio = BIO_new(BIO_s_mem());
+ RSA* public_key = nullptr;
+ EXPECT_EQ(public_key_serialized.size(),
+ BIO_puts(bio, public_key_serialized.c_str()));
+ public_key = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
+ EXPECT_TRUE(public_key) << "Could not load the public key from testdata.";
+
+ // Deserialize the signature.
+ string signature;
+ TF_EXPECT_OK(Base64Decode(signature_encoded, &signature));
+
+ // Actually cryptographically verify the signature.
+ const auto md = EVP_sha256();
+ auto md_ctx = EVP_MD_CTX_create();
+ auto key = EVP_PKEY_new();
+ EVP_PKEY_set1_RSA(key, public_key);
+ ASSERT_EQ(1, EVP_DigestVerifyInit(md_ctx, nullptr, md, nullptr, key));
+ ASSERT_EQ(1, EVP_DigestVerifyUpdate(md_ctx, header_dot_claim.c_str(),
+ header_dot_claim.size()));
+ ASSERT_EQ(
+ 1,
+ EVP_DigestVerifyFinal(
+ md_ctx, const_cast<unsigned char*>(
+ reinterpret_cast<const unsigned char*>(signature.data())),
+ signature.size()));
+ EVP_MD_CTX_cleanup(md_ctx);
+
+ // Free all the crypto-related resources.
+ EVP_PKEY_free(key);
+ EVP_MD_CTX_destroy(md_ctx);
+ RSA_free(public_key);
+ BIO_free_all(bio);
+
+ // Now check the content of the header and the claim.
+ int dot = header_dot_claim.find_last_of(".");
+ string header_encoded = header_dot_claim.substr(0, dot);
+ string claim_encoded = header_dot_claim.substr(dot + 1);
+
+ string header, claim;
+ TF_EXPECT_OK(Base64Decode(header_encoded, &header));
+ TF_EXPECT_OK(Base64Decode(claim_encoded, &claim));
+
+ Json::Value header_json, claim_json;
+ EXPECT_TRUE(reader.parse(header, header_json));
+ EXPECT_EQ("RS256", header_json.get("alg", Json::Value::null).asString());
+ EXPECT_EQ("JWT", header_json.get("typ", Json::Value::null).asString());
+ EXPECT_EQ("fake_key_id",
+ header_json.get("kid", Json::Value::null).asString());
+
+ EXPECT_TRUE(reader.parse(claim, claim_json));
+ EXPECT_EQ("fake-test-project.iam.gserviceaccount.com",
+ claim_json.get("iss", Json::Value::null).asString());
+ EXPECT_EQ("https://test-token-scope.com",
+ claim_json.get("scope", Json::Value::null).asString());
+ EXPECT_EQ("https://www.googleapis.com/oauth2/v3/token",
+ claim_json.get("aud", Json::Value::null).asString());
+ EXPECT_EQ(10000, claim_json.get("iat", Json::Value::null).asInt64());
+ EXPECT_EQ(13600, claim_json.get("exp", Json::Value::null).asInt64());
+}
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/testdata/application_default_credentials.json b/tensorflow/core/platform/cloud/testdata/application_default_credentials.json
new file mode 100644
index 0000000000..e8bcb20fad
--- /dev/null
+++ b/tensorflow/core/platform/cloud/testdata/application_default_credentials.json
@@ -0,0 +1,6 @@
+{
+ "client_id": "fake-client-id.apps.googleusercontent.com",
+ "client_secret": "fake-client-secret",
+ "refresh_token": "fake-refresh-token",
+ "type": "authorized_user"
+}
diff --git a/tensorflow/core/platform/cloud/testdata/service_account_credentials.json b/tensorflow/core/platform/cloud/testdata/service_account_credentials.json
new file mode 100644
index 0000000000..f0edd96e22
--- /dev/null
+++ b/tensorflow/core/platform/cloud/testdata/service_account_credentials.json
@@ -0,0 +1,12 @@
+{
+ "type": "service_account",
+ "project_id": "fake_project_id",
+ "private_key_id": "fake_key_id",
+ "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwrEZE6PWQYAy68mWPMuC6KAD02Sb9Pv/FHWpGKe8MxxdDiz/\nspb2KIrWxxZolStHgDXAOoElbAv4GbRLJiivEl8k0gSP9YpIE56nSxfXxRIDH25N\nI3fhRIs5hSG+/p3lLV5NsdNrm1CYHnEbTY7Ow7gpyxl0n+6q+ngguZTOGtBIMqVS\n4KIJlzTlJgeqvLFbtLP6uFc4OuGL6UZ+s4I7zSJVPBRxrFA+mOhBEPz/QjANBHBd\nIEhgh5VlmX/oRUK+D3zR/MnRTYtD8skiZSFMUix1eWvKw/1wX0mieH1rUQbpIYdJ\nTgFhROKuAJWVU7c+T6JHZwm8DqXaVz6oCJPlzwIDAQABAoIBAGHQVAb4A0b5P5wS\ntXZp0KVK72EfZPNaP7dpvcDzVKxhDad3mCeDjLyltG5lpbl7+vpBBwjdpY15Hfbc\nC/1p5ztVrcwOGr2D8d5ZkTc7DV6nRAZghkTRj82+HPH0GF8XuPJoNKSo0aFAhoyU\nyuDWZK8UMXsmmN9ZK3GXNOnIBxyUs703ueIgNkH9zlT2x0wmEs4toZKiPVZhLUrc\nG1zLfuf1onhB5xq7u0sYZCiJrvaVvzNrKune1IrBM+FK/dc3k0vF9NEvwCYxWuTj\nGwO2wU3U945Scj9718pxhMMxZpsPZfMZHrYcdMvjpPaKFhJjxb16kT4gvSdm015j\nLgpM1xECgYEA35/KW4npUPoltBZ2Gi/YPmGVfpyXz6ToOw9ENawiGdNrOQG1Pw+v\nPBV0+yvcp1AvlL46lp87xQrl0dYHwwsQ7eRqpeyG6PCXRN7pJXP9Dac6Tq07lu2g\nriltHcuw8WYLv0gjrNr8IaCN04VS30d8MayXgHuvR3+NHkBdryuKFgsCgYEA3uD7\nmNukdNxJBQhgOO8lCbLXdEjgFFDBuh/9GvpqaeILP4MIwpWj9tA9Hjw5JlK3qpHL\nvLsJinKMmaswX43Hzf8OAAhTkSC/TfIJwZTGuBPoDH4UnMD+83SAk8DDgWTUvz/6\n1ilR4zm3kus6ZxTA1zp3P5UFD2etbv+cmGkjHc0CgYBkpw1z6j0j/5Oc3UdHPiW8\n3jtlg6IpCfalLpfq+JFYwnpObGBiA/NBvf6rVvC4NjVUY9MHHKDQbblHm2he98ok\n6Vy/VhjbG/9aNmMGQpCx5oUuCHb71fUuruK4OIhp/x5meFfmY6J8mEF95VKJwSk7\nSo3efM1GBzlDVoFUaOp8RQKBgQDWBQ0Ul7WwUef8YTKk+V+DlKy4CVLDr1iYNieC\nRHzy+BD9CALdd3xfgU9vPT1Tw5KCxEX0EVb0D1NcLLrixu7arNTwyw4UCnIpkwYz\nUX4RPWxSsq9wZxNrDLB7MVuLYRu6GuHvzPXJUJ8rAZ6vZYpYIthnwd1+EXzFXcct\nw6fo8QKBgQClY0EmhGIoDHNPjPOGzl2hmZCm5FKPx9i2SOOVYuSMdPT3qTYOp4/Q\nUp1oqkbd1ZWxMlbuRljpwbUHRcj85O5bkmWylINjpA1hFqxcxtj1r9xRmeO9Qcqa\n89jOblkbSoVDE5CFHD0Cv4bFw09z/l6Ih9DOW4AlB5UN+byEUPsIdw==\n-----END RSA PRIVATE KEY-----",
+ "client_email": "fake-test-project.iam.gserviceaccount.com",
+ "client_id": "fake_client_id",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://accounts.google.com/o/oauth2/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/fake-test-project.iam.gserviceaccount.com"
+} \ No newline at end of file
diff --git a/tensorflow/core/platform/cloud/testdata/service_account_public_key.txt b/tensorflow/core/platform/cloud/testdata/service_account_public_key.txt
new file mode 100644
index 0000000000..25bd17d689
--- /dev/null
+++ b/tensorflow/core/platform/cloud/testdata/service_account_public_key.txt
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwrEZE6PWQYAy68mWPMuC
+6KAD02Sb9Pv/FHWpGKe8MxxdDiz/spb2KIrWxxZolStHgDXAOoElbAv4GbRLJiiv
+El8k0gSP9YpIE56nSxfXxRIDH25NI3fhRIs5hSG+/p3lLV5NsdNrm1CYHnEbTY7O
+w7gpyxl0n+6q+ngguZTOGtBIMqVS4KIJlzTlJgeqvLFbtLP6uFc4OuGL6UZ+s4I7
+zSJVPBRxrFA+mOhBEPz/QjANBHBdIEhgh5VlmX/oRUK+D3zR/MnRTYtD8skiZSFM
+Uix1eWvKw/1wX0mieH1rUQbpIYdJTgFhROKuAJWVU7c+T6JHZwm8DqXaVz6oCJPl
+zwIDAQAB
+-----END PUBLIC KEY----- \ No newline at end of file
diff --git a/tensorflow/core/platform/env.h b/tensorflow/core/platform/env.h
index dd1beb3246..4e35542600 100644
--- a/tensorflow/core/platform/env.h
+++ b/tensorflow/core/platform/env.h
@@ -162,6 +162,10 @@ class Env {
/// time. Only useful for computing deltas of time.
virtual uint64 NowMicros() = 0;
+ /// \brief Returns the number of seconds since some fixed point in
+ /// time. Only useful for computing deltas of time.
+ virtual uint64 NowSeconds() { return NowMicros() / 1000000L; }
+
/// Sleeps/delays the thread for the prescribed number of micro-seconds.
virtual void SleepForMicroseconds(int micros) = 0;
diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl
index 40b2017c75..d99cb5b5e3 100644
--- a/tensorflow/workspace.bzl
+++ b/tensorflow/workspace.bzl
@@ -120,3 +120,10 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""):
name = "jsoncpp",
actual = "@jsoncpp_git//:jsoncpp",
)
+
+ native.new_git_repository(
+ name = "boringssl_git",
+ commit = "e72df93461c6d9d2b5698f10e16d3ab82f5adde3",
+ remote = "https://boringssl.googlesource.com/boringssl",
+ build_file = path_prefix + "boringssl.BUILD",
+ )
diff --git a/third_party/boringssl/BUILD b/third_party/boringssl/BUILD
new file mode 100644
index 0000000000..3211d7ae97
--- /dev/null
+++ b/third_party/boringssl/BUILD
@@ -0,0 +1,13 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["restricted"]) # OpenSSL license, partly BSD-like
+
+# See https://boringssl.googlesource.com/boringssl/+/master/INCORPORATING.md
+# on how to re-generate err_data.c.
+
+filegroup(
+ name = "err_data_c",
+ srcs = [
+ "err_data.c",
+ ],
+)
diff --git a/third_party/boringssl/err_data.c b/third_party/boringssl/err_data.c
new file mode 100644
index 0000000000..2d5fed6c1f
--- /dev/null
+++ b/third_party/boringssl/err_data.c
@@ -0,0 +1,1236 @@
+/* Copyright (c) 2015, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+ /* This file was generated by err_data_generate.go. */
+
+#include <openssl/base.h>
+#include <openssl/err.h>
+#include <openssl/type_check.h>
+
+
+OPENSSL_COMPILE_ASSERT(ERR_LIB_NONE == 1, library_values_changed_1);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_SYS == 2, library_values_changed_2);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_BN == 3, library_values_changed_3);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_RSA == 4, library_values_changed_4);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_DH == 5, library_values_changed_5);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_EVP == 6, library_values_changed_6);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_BUF == 7, library_values_changed_7);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_OBJ == 8, library_values_changed_8);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_PEM == 9, library_values_changed_9);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_DSA == 10, library_values_changed_10);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_X509 == 11, library_values_changed_11);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_ASN1 == 12, library_values_changed_12);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_CONF == 13, library_values_changed_13);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_CRYPTO == 14, library_values_changed_14);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_EC == 15, library_values_changed_15);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_SSL == 16, library_values_changed_16);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_BIO == 17, library_values_changed_17);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_PKCS7 == 18, library_values_changed_18);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_PKCS8 == 19, library_values_changed_19);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_X509V3 == 20, library_values_changed_20);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_RAND == 21, library_values_changed_21);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_ENGINE == 22, library_values_changed_22);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_OCSP == 23, library_values_changed_23);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_UI == 24, library_values_changed_24);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_COMP == 25, library_values_changed_25);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_ECDSA == 26, library_values_changed_26);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_ECDH == 27, library_values_changed_27);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_HMAC == 28, library_values_changed_28);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_DIGEST == 29, library_values_changed_29);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_CIPHER == 30, library_values_changed_30);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_HKDF == 31, library_values_changed_31);
+OPENSSL_COMPILE_ASSERT(ERR_LIB_USER == 32, library_values_changed_32);
+OPENSSL_COMPILE_ASSERT(ERR_NUM_LIBS == 33, library_values_changed_num);
+
+const uint32_t kOpenSSLReasonValues[] = {
+ 0xc320838,
+ 0xc328852,
+ 0xc330861,
+ 0xc338871,
+ 0xc340880,
+ 0xc348899,
+ 0xc3508a5,
+ 0xc3588c2,
+ 0xc3608d4,
+ 0xc3688e2,
+ 0xc3708f2,
+ 0xc3788ff,
+ 0xc38090f,
+ 0xc38891a,
+ 0xc390930,
+ 0xc39893f,
+ 0xc3a0953,
+ 0xc3a8845,
+ 0xc3b00ea,
+ 0x10320845,
+ 0x1032939a,
+ 0x103313a6,
+ 0x103393bf,
+ 0x103413d2,
+ 0x10348e7a,
+ 0x10350c19,
+ 0x103593e5,
+ 0x103613fa,
+ 0x1036940d,
+ 0x1037142c,
+ 0x10379445,
+ 0x1038145a,
+ 0x10389478,
+ 0x10391487,
+ 0x103994a3,
+ 0x103a14be,
+ 0x103a94cd,
+ 0x103b14e9,
+ 0x103b9504,
+ 0x103c151b,
+ 0x103c80ea,
+ 0x103d152c,
+ 0x103d9540,
+ 0x103e155f,
+ 0x103e956e,
+ 0x103f1585,
+ 0x103f9598,
+ 0x10400bea,
+ 0x104095ab,
+ 0x104115c9,
+ 0x104195dc,
+ 0x104215f6,
+ 0x10429606,
+ 0x1043161a,
+ 0x10439630,
+ 0x10441648,
+ 0x1044965d,
+ 0x10451671,
+ 0x10459683,
+ 0x104605fb,
+ 0x1046893f,
+ 0x10471698,
+ 0x104796af,
+ 0x104816c4,
+ 0x104896d2,
+ 0x14320bcd,
+ 0x14328bdb,
+ 0x14330bea,
+ 0x14338bfc,
+ 0x18320083,
+ 0x18328ed0,
+ 0x183300ac,
+ 0x18338ee6,
+ 0x18340efa,
+ 0x183480ea,
+ 0x18350f0f,
+ 0x18358f27,
+ 0x18360f3c,
+ 0x18368f50,
+ 0x18370f74,
+ 0x18378f8a,
+ 0x18380f9e,
+ 0x18388fae,
+ 0x18390a57,
+ 0x18398fbe,
+ 0x183a0fd3,
+ 0x183a8fe7,
+ 0x183b0c25,
+ 0x183b8ff4,
+ 0x183c1006,
+ 0x183c9011,
+ 0x183d1021,
+ 0x183d9032,
+ 0x183e1043,
+ 0x183e9055,
+ 0x183f107e,
+ 0x183f9097,
+ 0x184010af,
+ 0x184086d3,
+ 0x203210d6,
+ 0x243210e2,
+ 0x24328985,
+ 0x243310f4,
+ 0x24339101,
+ 0x2434110e,
+ 0x24349120,
+ 0x2435112f,
+ 0x2435914c,
+ 0x24361159,
+ 0x24369167,
+ 0x24371175,
+ 0x24379183,
+ 0x2438118c,
+ 0x24389199,
+ 0x243911ac,
+ 0x28320c0d,
+ 0x28328c25,
+ 0x28330bea,
+ 0x28338c38,
+ 0x28340c19,
+ 0x283480ac,
+ 0x283500ea,
+ 0x2c32274a,
+ 0x2c32a758,
+ 0x2c33276a,
+ 0x2c33a77c,
+ 0x2c342790,
+ 0x2c34a7a2,
+ 0x2c3527bd,
+ 0x2c35a7cf,
+ 0x2c3627e2,
+ 0x2c36832d,
+ 0x2c3727ef,
+ 0x2c37a801,
+ 0x2c382814,
+ 0x2c38a82b,
+ 0x2c392839,
+ 0x2c39a849,
+ 0x2c3a285b,
+ 0x2c3aa86f,
+ 0x2c3b2880,
+ 0x2c3ba89f,
+ 0x2c3c28b3,
+ 0x2c3ca8c9,
+ 0x2c3d28e2,
+ 0x2c3da8ff,
+ 0x2c3e2910,
+ 0x2c3ea91e,
+ 0x2c3f2936,
+ 0x2c3fa94e,
+ 0x2c40295b,
+ 0x2c4090d6,
+ 0x2c41296c,
+ 0x2c41a97f,
+ 0x2c4210af,
+ 0x2c42a990,
+ 0x2c430720,
+ 0x2c43a891,
+ 0x30320000,
+ 0x30328015,
+ 0x3033001f,
+ 0x30338038,
+ 0x3034004a,
+ 0x30348064,
+ 0x3035006b,
+ 0x30358083,
+ 0x30360094,
+ 0x303680ac,
+ 0x303700b9,
+ 0x303780c8,
+ 0x303800ea,
+ 0x303880f7,
+ 0x3039010a,
+ 0x30398125,
+ 0x303a013a,
+ 0x303a814e,
+ 0x303b0162,
+ 0x303b8173,
+ 0x303c018c,
+ 0x303c81a9,
+ 0x303d01b7,
+ 0x303d81cb,
+ 0x303e01db,
+ 0x303e81f4,
+ 0x303f0204,
+ 0x303f8217,
+ 0x30400226,
+ 0x30408232,
+ 0x30410247,
+ 0x30418257,
+ 0x3042026e,
+ 0x3042827b,
+ 0x3043028e,
+ 0x3043829d,
+ 0x304402b2,
+ 0x304482d3,
+ 0x304502e6,
+ 0x304582f9,
+ 0x30460312,
+ 0x3046832d,
+ 0x3047034a,
+ 0x30478363,
+ 0x30480371,
+ 0x30488382,
+ 0x30490391,
+ 0x304983a9,
+ 0x304a03bb,
+ 0x304a83cf,
+ 0x304b03ee,
+ 0x304b8401,
+ 0x304c040c,
+ 0x304c841d,
+ 0x304d0429,
+ 0x304d843f,
+ 0x304e044d,
+ 0x304e8463,
+ 0x304f0475,
+ 0x304f8487,
+ 0x3050049a,
+ 0x305084ad,
+ 0x305104be,
+ 0x305184ce,
+ 0x305204e6,
+ 0x305284fb,
+ 0x30530513,
+ 0x30538527,
+ 0x3054053f,
+ 0x30548558,
+ 0x30550571,
+ 0x3055858e,
+ 0x30560599,
+ 0x305685b1,
+ 0x305705c1,
+ 0x305785d2,
+ 0x305805e5,
+ 0x305885fb,
+ 0x30590604,
+ 0x30598619,
+ 0x305a062c,
+ 0x305a863b,
+ 0x305b065b,
+ 0x305b866a,
+ 0x305c068b,
+ 0x305c86a7,
+ 0x305d06b3,
+ 0x305d86d3,
+ 0x305e06ef,
+ 0x305e8700,
+ 0x305f0716,
+ 0x305f8720,
+ 0x34320b47,
+ 0x34328b5b,
+ 0x34330b78,
+ 0x34338b8b,
+ 0x34340b9a,
+ 0x34348bb7,
+ 0x3c320083,
+ 0x3c328c62,
+ 0x3c330c7b,
+ 0x3c338c96,
+ 0x3c340cb3,
+ 0x3c348cdd,
+ 0x3c350cf8,
+ 0x3c358d0d,
+ 0x3c360d26,
+ 0x3c368d3e,
+ 0x3c370d4f,
+ 0x3c378d5d,
+ 0x3c380d6a,
+ 0x3c388d7e,
+ 0x3c390c25,
+ 0x3c398d92,
+ 0x3c3a0da6,
+ 0x3c3a88ff,
+ 0x3c3b0db6,
+ 0x3c3b8dd1,
+ 0x3c3c0de3,
+ 0x3c3c8df9,
+ 0x3c3d0e03,
+ 0x3c3d8e17,
+ 0x3c3e0e25,
+ 0x3c3e8e4a,
+ 0x3c3f0c4e,
+ 0x3c3f8e33,
+ 0x3c4000ac,
+ 0x3c4080ea,
+ 0x3c410cce,
+ 0x403216e9,
+ 0x403296ff,
+ 0x4033172d,
+ 0x40339737,
+ 0x4034174e,
+ 0x4034976c,
+ 0x4035177c,
+ 0x4035978e,
+ 0x4036179b,
+ 0x403697a7,
+ 0x403717bc,
+ 0x403797ce,
+ 0x403817d9,
+ 0x403897eb,
+ 0x40390e7a,
+ 0x403997fb,
+ 0x403a180e,
+ 0x403a982f,
+ 0x403b1840,
+ 0x403b9850,
+ 0x403c0064,
+ 0x403c8083,
+ 0x403d185c,
+ 0x403d9872,
+ 0x403e1881,
+ 0x403e9894,
+ 0x403f18ae,
+ 0x403f98bc,
+ 0x404018d1,
+ 0x404098e5,
+ 0x40411902,
+ 0x4041991d,
+ 0x40421936,
+ 0x40429949,
+ 0x4043195d,
+ 0x40439975,
+ 0x4044198c,
+ 0x404480ac,
+ 0x404519a1,
+ 0x404599b3,
+ 0x404619d7,
+ 0x404699f7,
+ 0x40471a05,
+ 0x40479a19,
+ 0x40481a2e,
+ 0x40489a47,
+ 0x40491a5e,
+ 0x40499a78,
+ 0x404a1a8f,
+ 0x404a9aad,
+ 0x404b1ac5,
+ 0x404b9adc,
+ 0x404c1af2,
+ 0x404c9b04,
+ 0x404d1b25,
+ 0x404d9b47,
+ 0x404e1b5b,
+ 0x404e9b68,
+ 0x404f1b7f,
+ 0x404f9b8f,
+ 0x40501b9f,
+ 0x40509bb3,
+ 0x40511bce,
+ 0x40519bde,
+ 0x40521bf5,
+ 0x40529c07,
+ 0x40531c1f,
+ 0x40539c32,
+ 0x40541c47,
+ 0x40549c6a,
+ 0x40551c78,
+ 0x40559c95,
+ 0x40561ca2,
+ 0x40569cbb,
+ 0x40571cd3,
+ 0x40579ce6,
+ 0x40581cfb,
+ 0x40589d0d,
+ 0x40591d1d,
+ 0x40599d36,
+ 0x405a1d4a,
+ 0x405a9d5a,
+ 0x405b1d72,
+ 0x405b9d83,
+ 0x405c1d96,
+ 0x405c9da7,
+ 0x405d1db4,
+ 0x405d9dcb,
+ 0x405e1deb,
+ 0x405e8a95,
+ 0x405f1e0c,
+ 0x405f9e19,
+ 0x40601e27,
+ 0x40609e49,
+ 0x40611e71,
+ 0x40619e86,
+ 0x40621e9d,
+ 0x40629eae,
+ 0x40631ebf,
+ 0x40639ed4,
+ 0x40641eeb,
+ 0x40649efc,
+ 0x40651f17,
+ 0x40659f2e,
+ 0x40661f46,
+ 0x40669f70,
+ 0x40671f9b,
+ 0x40679fbc,
+ 0x40681fcf,
+ 0x40689ff0,
+ 0x40692022,
+ 0x4069a050,
+ 0x406a2071,
+ 0x406aa091,
+ 0x406b2219,
+ 0x406ba23c,
+ 0x406c2252,
+ 0x406ca47e,
+ 0x406d24ad,
+ 0x406da4d5,
+ 0x406e24ee,
+ 0x406ea506,
+ 0x406f2525,
+ 0x406fa53a,
+ 0x4070254d,
+ 0x4070a56a,
+ 0x40710800,
+ 0x4071a57c,
+ 0x4072258f,
+ 0x4072a5a8,
+ 0x407325c0,
+ 0x4073935c,
+ 0x407425d4,
+ 0x4074a5ee,
+ 0x407525ff,
+ 0x4075a613,
+ 0x40762621,
+ 0x40769199,
+ 0x40772646,
+ 0x4077a668,
+ 0x40782683,
+ 0x4078a698,
+ 0x407926af,
+ 0x4079a6c5,
+ 0x407a26d1,
+ 0x407aa6e4,
+ 0x407b26f9,
+ 0x407ba70b,
+ 0x407c2720,
+ 0x407ca729,
+ 0x407d200b,
+ 0x41f42144,
+ 0x41f921d6,
+ 0x41fe20c9,
+ 0x41fea2a5,
+ 0x41ff2396,
+ 0x4203215d,
+ 0x4208217f,
+ 0x4208a1bb,
+ 0x420920ad,
+ 0x4209a1f5,
+ 0x420a2104,
+ 0x420aa0e4,
+ 0x420b2124,
+ 0x420ba19d,
+ 0x420c23b2,
+ 0x420ca272,
+ 0x420d228c,
+ 0x420da2c3,
+ 0x421222dd,
+ 0x42172379,
+ 0x4217a31f,
+ 0x421c2341,
+ 0x421f22fc,
+ 0x422123c9,
+ 0x4226235c,
+ 0x422b2462,
+ 0x422ba42b,
+ 0x422c244a,
+ 0x422ca405,
+ 0x422d23e4,
+ 0x4432072b,
+ 0x4432873a,
+ 0x44330746,
+ 0x44338754,
+ 0x44340767,
+ 0x44348778,
+ 0x4435077f,
+ 0x44358789,
+ 0x4436079c,
+ 0x443687b2,
+ 0x443707c4,
+ 0x443787d1,
+ 0x443807e0,
+ 0x443887e8,
+ 0x44390800,
+ 0x4439880e,
+ 0x443a0821,
+ 0x4c3211c3,
+ 0x4c3291d3,
+ 0x4c3311e6,
+ 0x4c339206,
+ 0x4c3400ac,
+ 0x4c3480ea,
+ 0x4c351212,
+ 0x4c359220,
+ 0x4c36123c,
+ 0x4c36924f,
+ 0x4c37125e,
+ 0x4c37926c,
+ 0x4c381281,
+ 0x4c38928d,
+ 0x4c3912ad,
+ 0x4c3992d7,
+ 0x4c3a12f0,
+ 0x4c3a9309,
+ 0x4c3b05fb,
+ 0x4c3b9322,
+ 0x4c3c1334,
+ 0x4c3c9343,
+ 0x4c3d135c,
+ 0x4c3d936b,
+ 0x4c3e1378,
+ 0x503229a2,
+ 0x5032a9b1,
+ 0x503329bc,
+ 0x5033a9cc,
+ 0x503429e5,
+ 0x5034a9ff,
+ 0x50352a0d,
+ 0x5035aa23,
+ 0x50362a35,
+ 0x5036aa4b,
+ 0x50372a64,
+ 0x5037aa77,
+ 0x50382a8f,
+ 0x5038aaa0,
+ 0x50392ab5,
+ 0x5039aac9,
+ 0x503a2ae9,
+ 0x503aaaff,
+ 0x503b2b17,
+ 0x503bab29,
+ 0x503c2b45,
+ 0x503cab5c,
+ 0x503d2b75,
+ 0x503dab8b,
+ 0x503e2b98,
+ 0x503eabae,
+ 0x503f2bc0,
+ 0x503f8382,
+ 0x50402bd3,
+ 0x5040abe3,
+ 0x50412bfd,
+ 0x5041ac0c,
+ 0x50422c26,
+ 0x5042ac43,
+ 0x50432c53,
+ 0x5043ac63,
+ 0x50442c72,
+ 0x5044843f,
+ 0x50452c86,
+ 0x5045aca4,
+ 0x50462cb7,
+ 0x5046accd,
+ 0x50472cdf,
+ 0x5047acf4,
+ 0x50482d1a,
+ 0x5048ad28,
+ 0x50492d3b,
+ 0x5049ad50,
+ 0x504a2d66,
+ 0x504aad76,
+ 0x504b2d96,
+ 0x504bada9,
+ 0x504c2dcc,
+ 0x504cadfa,
+ 0x504d2e0c,
+ 0x504dae29,
+ 0x504e2e44,
+ 0x504eae60,
+ 0x504f2e72,
+ 0x504fae89,
+ 0x50502e98,
+ 0x505086ef,
+ 0x50512eab,
+ 0x58320eb8,
+ 0x68320e7a,
+ 0x68328c25,
+ 0x68330c38,
+ 0x68338e88,
+ 0x68340e98,
+ 0x683480ea,
+ 0x6c320e56,
+ 0x6c328bfc,
+ 0x6c330e61,
+ 0x74320a0b,
+ 0x78320970,
+ 0x78328985,
+ 0x78330991,
+ 0x78338083,
+ 0x783409a0,
+ 0x783489b5,
+ 0x783509d4,
+ 0x783589f6,
+ 0x78360a0b,
+ 0x78368a21,
+ 0x78370a31,
+ 0x78378a44,
+ 0x78380a57,
+ 0x78388a69,
+ 0x78390a76,
+ 0x78398a95,
+ 0x783a0aaa,
+ 0x783a8ab8,
+ 0x783b0ac2,
+ 0x783b8ad6,
+ 0x783c0aed,
+ 0x783c8b02,
+ 0x783d0b19,
+ 0x783d8b2e,
+ 0x783e0a84,
+ 0x7c3210c5,
+};
+
+const size_t kOpenSSLReasonValuesLen = sizeof(kOpenSSLReasonValues) / sizeof(kOpenSSLReasonValues[0]);
+
+const char kOpenSSLReasonStringData[] =
+ "ASN1_LENGTH_MISMATCH\0"
+ "AUX_ERROR\0"
+ "BAD_GET_ASN1_OBJECT_CALL\0"
+ "BAD_OBJECT_HEADER\0"
+ "BMPSTRING_IS_WRONG_LENGTH\0"
+ "BN_LIB\0"
+ "BOOLEAN_IS_WRONG_LENGTH\0"
+ "BUFFER_TOO_SMALL\0"
+ "CONTEXT_NOT_INITIALISED\0"
+ "DECODE_ERROR\0"
+ "DEPTH_EXCEEDED\0"
+ "DIGEST_AND_KEY_TYPE_NOT_SUPPORTED\0"
+ "ENCODE_ERROR\0"
+ "ERROR_GETTING_TIME\0"
+ "EXPECTING_AN_ASN1_SEQUENCE\0"
+ "EXPECTING_AN_INTEGER\0"
+ "EXPECTING_AN_OBJECT\0"
+ "EXPECTING_A_BOOLEAN\0"
+ "EXPECTING_A_TIME\0"
+ "EXPLICIT_LENGTH_MISMATCH\0"
+ "EXPLICIT_TAG_NOT_CONSTRUCTED\0"
+ "FIELD_MISSING\0"
+ "FIRST_NUM_TOO_LARGE\0"
+ "HEADER_TOO_LONG\0"
+ "ILLEGAL_BITSTRING_FORMAT\0"
+ "ILLEGAL_BOOLEAN\0"
+ "ILLEGAL_CHARACTERS\0"
+ "ILLEGAL_FORMAT\0"
+ "ILLEGAL_HEX\0"
+ "ILLEGAL_IMPLICIT_TAG\0"
+ "ILLEGAL_INTEGER\0"
+ "ILLEGAL_NESTED_TAGGING\0"
+ "ILLEGAL_NULL\0"
+ "ILLEGAL_NULL_VALUE\0"
+ "ILLEGAL_OBJECT\0"
+ "ILLEGAL_OPTIONAL_ANY\0"
+ "ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE\0"
+ "ILLEGAL_TAGGED_ANY\0"
+ "ILLEGAL_TIME_VALUE\0"
+ "INTEGER_NOT_ASCII_FORMAT\0"
+ "INTEGER_TOO_LARGE_FOR_LONG\0"
+ "INVALID_BIT_STRING_BITS_LEFT\0"
+ "INVALID_BMPSTRING_LENGTH\0"
+ "INVALID_DIGIT\0"
+ "INVALID_MODIFIER\0"
+ "INVALID_NUMBER\0"
+ "INVALID_OBJECT_ENCODING\0"
+ "INVALID_SEPARATOR\0"
+ "INVALID_TIME_FORMAT\0"
+ "INVALID_UNIVERSALSTRING_LENGTH\0"
+ "INVALID_UTF8STRING\0"
+ "LIST_ERROR\0"
+ "MISSING_ASN1_EOS\0"
+ "MISSING_EOC\0"
+ "MISSING_SECOND_NUMBER\0"
+ "MISSING_VALUE\0"
+ "MSTRING_NOT_UNIVERSAL\0"
+ "MSTRING_WRONG_TAG\0"
+ "NESTED_ASN1_ERROR\0"
+ "NESTED_ASN1_STRING\0"
+ "NON_HEX_CHARACTERS\0"
+ "NOT_ASCII_FORMAT\0"
+ "NOT_ENOUGH_DATA\0"
+ "NO_MATCHING_CHOICE_TYPE\0"
+ "NULL_IS_WRONG_LENGTH\0"
+ "OBJECT_NOT_ASCII_FORMAT\0"
+ "ODD_NUMBER_OF_CHARS\0"
+ "SECOND_NUMBER_TOO_LARGE\0"
+ "SEQUENCE_LENGTH_MISMATCH\0"
+ "SEQUENCE_NOT_CONSTRUCTED\0"
+ "SEQUENCE_OR_SET_NEEDS_CONFIG\0"
+ "SHORT_LINE\0"
+ "STREAMING_NOT_SUPPORTED\0"
+ "STRING_TOO_LONG\0"
+ "STRING_TOO_SHORT\0"
+ "TAG_VALUE_TOO_HIGH\0"
+ "TIME_NOT_ASCII_FORMAT\0"
+ "TOO_LONG\0"
+ "TYPE_NOT_CONSTRUCTED\0"
+ "TYPE_NOT_PRIMITIVE\0"
+ "UNEXPECTED_EOC\0"
+ "UNIVERSALSTRING_IS_WRONG_LENGTH\0"
+ "UNKNOWN_FORMAT\0"
+ "UNKNOWN_MESSAGE_DIGEST_ALGORITHM\0"
+ "UNKNOWN_SIGNATURE_ALGORITHM\0"
+ "UNKNOWN_TAG\0"
+ "UNSUPPORTED_ANY_DEFINED_BY_TYPE\0"
+ "UNSUPPORTED_PUBLIC_KEY_TYPE\0"
+ "UNSUPPORTED_TYPE\0"
+ "WRONG_PUBLIC_KEY_TYPE\0"
+ "WRONG_TAG\0"
+ "WRONG_TYPE\0"
+ "BAD_FOPEN_MODE\0"
+ "BROKEN_PIPE\0"
+ "CONNECT_ERROR\0"
+ "ERROR_SETTING_NBIO\0"
+ "INVALID_ARGUMENT\0"
+ "IN_USE\0"
+ "KEEPALIVE\0"
+ "NBIO_CONNECT_ERROR\0"
+ "NO_HOSTNAME_SPECIFIED\0"
+ "NO_PORT_SPECIFIED\0"
+ "NO_SUCH_FILE\0"
+ "NULL_PARAMETER\0"
+ "SYS_LIB\0"
+ "UNABLE_TO_CREATE_SOCKET\0"
+ "UNINITIALIZED\0"
+ "UNSUPPORTED_METHOD\0"
+ "WRITE_TO_READ_ONLY_BIO\0"
+ "ARG2_LT_ARG3\0"
+ "BAD_ENCODING\0"
+ "BAD_RECIPROCAL\0"
+ "BIGNUM_TOO_LONG\0"
+ "BITS_TOO_SMALL\0"
+ "CALLED_WITH_EVEN_MODULUS\0"
+ "DIV_BY_ZERO\0"
+ "EXPAND_ON_STATIC_BIGNUM_DATA\0"
+ "INPUT_NOT_REDUCED\0"
+ "INVALID_RANGE\0"
+ "NEGATIVE_NUMBER\0"
+ "NOT_A_SQUARE\0"
+ "NOT_INITIALIZED\0"
+ "NO_INVERSE\0"
+ "PRIVATE_KEY_TOO_LARGE\0"
+ "P_IS_NOT_PRIME\0"
+ "TOO_MANY_ITERATIONS\0"
+ "TOO_MANY_TEMPORARY_VARIABLES\0"
+ "AES_KEY_SETUP_FAILED\0"
+ "BAD_DECRYPT\0"
+ "BAD_KEY_LENGTH\0"
+ "CTRL_NOT_IMPLEMENTED\0"
+ "CTRL_OPERATION_NOT_IMPLEMENTED\0"
+ "DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH\0"
+ "INITIALIZATION_ERROR\0"
+ "INPUT_NOT_INITIALIZED\0"
+ "INVALID_AD_SIZE\0"
+ "INVALID_KEY_LENGTH\0"
+ "INVALID_NONCE_SIZE\0"
+ "INVALID_OPERATION\0"
+ "IV_TOO_LARGE\0"
+ "NO_CIPHER_SET\0"
+ "NO_DIRECTION_SET\0"
+ "OUTPUT_ALIASES_INPUT\0"
+ "TAG_TOO_LARGE\0"
+ "TOO_LARGE\0"
+ "UNSUPPORTED_AD_SIZE\0"
+ "UNSUPPORTED_INPUT_SIZE\0"
+ "UNSUPPORTED_KEY_SIZE\0"
+ "UNSUPPORTED_NONCE_SIZE\0"
+ "UNSUPPORTED_TAG_SIZE\0"
+ "WRONG_FINAL_BLOCK_LENGTH\0"
+ "LIST_CANNOT_BE_NULL\0"
+ "MISSING_CLOSE_SQUARE_BRACKET\0"
+ "MISSING_EQUAL_SIGN\0"
+ "NO_CLOSE_BRACE\0"
+ "UNABLE_TO_CREATE_NEW_SECTION\0"
+ "VARIABLE_HAS_NO_VALUE\0"
+ "BAD_GENERATOR\0"
+ "INVALID_PUBKEY\0"
+ "MODULUS_TOO_LARGE\0"
+ "NO_PRIVATE_VALUE\0"
+ "BAD_Q_VALUE\0"
+ "BAD_VERSION\0"
+ "MISSING_PARAMETERS\0"
+ "NEED_NEW_SETUP_VALUES\0"
+ "BIGNUM_OUT_OF_RANGE\0"
+ "COORDINATES_OUT_OF_RANGE\0"
+ "D2I_ECPKPARAMETERS_FAILURE\0"
+ "EC_GROUP_NEW_BY_NAME_FAILURE\0"
+ "GROUP2PKPARAMETERS_FAILURE\0"
+ "GROUP_MISMATCH\0"
+ "I2D_ECPKPARAMETERS_FAILURE\0"
+ "INCOMPATIBLE_OBJECTS\0"
+ "INVALID_COMPRESSED_POINT\0"
+ "INVALID_COMPRESSION_BIT\0"
+ "INVALID_ENCODING\0"
+ "INVALID_FIELD\0"
+ "INVALID_FORM\0"
+ "INVALID_GROUP_ORDER\0"
+ "INVALID_PRIVATE_KEY\0"
+ "MISSING_PRIVATE_KEY\0"
+ "NON_NAMED_CURVE\0"
+ "PKPARAMETERS2GROUP_FAILURE\0"
+ "POINT_AT_INFINITY\0"
+ "POINT_IS_NOT_ON_CURVE\0"
+ "SLOT_FULL\0"
+ "UNDEFINED_GENERATOR\0"
+ "UNKNOWN_GROUP\0"
+ "UNKNOWN_ORDER\0"
+ "WRONG_CURVE_PARAMETERS\0"
+ "WRONG_ORDER\0"
+ "KDF_FAILED\0"
+ "POINT_ARITHMETIC_FAILURE\0"
+ "BAD_SIGNATURE\0"
+ "NOT_IMPLEMENTED\0"
+ "RANDOM_NUMBER_GENERATION_FAILED\0"
+ "OPERATION_NOT_SUPPORTED\0"
+ "COMMAND_NOT_SUPPORTED\0"
+ "DIFFERENT_KEY_TYPES\0"
+ "DIFFERENT_PARAMETERS\0"
+ "EXPECTING_AN_EC_KEY_KEY\0"
+ "EXPECTING_AN_RSA_KEY\0"
+ "EXPECTING_A_DSA_KEY\0"
+ "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE\0"
+ "INVALID_DIGEST_LENGTH\0"
+ "INVALID_DIGEST_TYPE\0"
+ "INVALID_KEYBITS\0"
+ "INVALID_MGF1_MD\0"
+ "INVALID_PADDING_MODE\0"
+ "INVALID_PSS_SALTLEN\0"
+ "KEYS_NOT_SET\0"
+ "NO_DEFAULT_DIGEST\0"
+ "NO_KEY_SET\0"
+ "NO_MDC2_SUPPORT\0"
+ "NO_NID_FOR_CURVE\0"
+ "NO_OPERATION_SET\0"
+ "NO_PARAMETERS_SET\0"
+ "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE\0"
+ "OPERATON_NOT_INITIALIZED\0"
+ "UNKNOWN_PUBLIC_KEY_TYPE\0"
+ "UNSUPPORTED_ALGORITHM\0"
+ "OUTPUT_TOO_LARGE\0"
+ "UNKNOWN_NID\0"
+ "BAD_BASE64_DECODE\0"
+ "BAD_END_LINE\0"
+ "BAD_IV_CHARS\0"
+ "BAD_PASSWORD_READ\0"
+ "CIPHER_IS_NULL\0"
+ "ERROR_CONVERTING_PRIVATE_KEY\0"
+ "NOT_DEK_INFO\0"
+ "NOT_ENCRYPTED\0"
+ "NOT_PROC_TYPE\0"
+ "NO_START_LINE\0"
+ "READ_KEY\0"
+ "SHORT_HEADER\0"
+ "UNSUPPORTED_CIPHER\0"
+ "UNSUPPORTED_ENCRYPTION\0"
+ "BAD_PKCS12_DATA\0"
+ "BAD_PKCS12_VERSION\0"
+ "CIPHER_HAS_NO_OBJECT_IDENTIFIER\0"
+ "CRYPT_ERROR\0"
+ "ENCRYPT_ERROR\0"
+ "ERROR_SETTING_CIPHER_PARAMS\0"
+ "INCORRECT_PASSWORD\0"
+ "KEYGEN_FAILURE\0"
+ "KEY_GEN_ERROR\0"
+ "METHOD_NOT_SUPPORTED\0"
+ "MISSING_MAC\0"
+ "MULTIPLE_PRIVATE_KEYS_IN_PKCS12\0"
+ "PKCS12_PUBLIC_KEY_INTEGRITY_NOT_SUPPORTED\0"
+ "PKCS12_TOO_DEEPLY_NESTED\0"
+ "PRIVATE_KEY_DECODE_ERROR\0"
+ "PRIVATE_KEY_ENCODE_ERROR\0"
+ "UNKNOWN_ALGORITHM\0"
+ "UNKNOWN_CIPHER\0"
+ "UNKNOWN_CIPHER_ALGORITHM\0"
+ "UNKNOWN_DIGEST\0"
+ "UNKNOWN_HASH\0"
+ "UNSUPPORTED_PRIVATE_KEY_ALGORITHM\0"
+ "BAD_E_VALUE\0"
+ "BAD_FIXED_HEADER_DECRYPT\0"
+ "BAD_PAD_BYTE_COUNT\0"
+ "BAD_RSA_PARAMETERS\0"
+ "BLOCK_TYPE_IS_NOT_01\0"
+ "BN_NOT_INITIALIZED\0"
+ "CANNOT_RECOVER_MULTI_PRIME_KEY\0"
+ "CRT_PARAMS_ALREADY_GIVEN\0"
+ "CRT_VALUES_INCORRECT\0"
+ "DATA_LEN_NOT_EQUAL_TO_MOD_LEN\0"
+ "DATA_TOO_LARGE\0"
+ "DATA_TOO_LARGE_FOR_KEY_SIZE\0"
+ "DATA_TOO_LARGE_FOR_MODULUS\0"
+ "DATA_TOO_SMALL\0"
+ "DATA_TOO_SMALL_FOR_KEY_SIZE\0"
+ "DIGEST_TOO_BIG_FOR_RSA_KEY\0"
+ "D_E_NOT_CONGRUENT_TO_1\0"
+ "EMPTY_PUBLIC_KEY\0"
+ "FIRST_OCTET_INVALID\0"
+ "INCONSISTENT_SET_OF_CRT_VALUES\0"
+ "INTERNAL_ERROR\0"
+ "INVALID_MESSAGE_LENGTH\0"
+ "KEY_SIZE_TOO_SMALL\0"
+ "LAST_OCTET_INVALID\0"
+ "MUST_HAVE_AT_LEAST_TWO_PRIMES\0"
+ "NO_PUBLIC_EXPONENT\0"
+ "NULL_BEFORE_BLOCK_MISSING\0"
+ "N_NOT_EQUAL_P_Q\0"
+ "OAEP_DECODING_ERROR\0"
+ "ONLY_ONE_OF_P_Q_GIVEN\0"
+ "OUTPUT_BUFFER_TOO_SMALL\0"
+ "PADDING_CHECK_FAILED\0"
+ "PKCS_DECODING_ERROR\0"
+ "SLEN_CHECK_FAILED\0"
+ "SLEN_RECOVERY_FAILED\0"
+ "UNKNOWN_ALGORITHM_TYPE\0"
+ "UNKNOWN_PADDING_TYPE\0"
+ "VALUE_MISSING\0"
+ "WRONG_SIGNATURE_LENGTH\0"
+ "APP_DATA_IN_HANDSHAKE\0"
+ "ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT\0"
+ "BAD_ALERT\0"
+ "BAD_CHANGE_CIPHER_SPEC\0"
+ "BAD_DATA_RETURNED_BY_CALLBACK\0"
+ "BAD_DH_P_LENGTH\0"
+ "BAD_DIGEST_LENGTH\0"
+ "BAD_ECC_CERT\0"
+ "BAD_ECPOINT\0"
+ "BAD_HANDSHAKE_RECORD\0"
+ "BAD_HELLO_REQUEST\0"
+ "BAD_LENGTH\0"
+ "BAD_PACKET_LENGTH\0"
+ "BAD_RSA_ENCRYPT\0"
+ "BAD_SRTP_MKI_VALUE\0"
+ "BAD_SRTP_PROTECTION_PROFILE_LIST\0"
+ "BAD_SSL_FILETYPE\0"
+ "BAD_WRITE_RETRY\0"
+ "BIO_NOT_SET\0"
+ "CA_DN_LENGTH_MISMATCH\0"
+ "CA_DN_TOO_LONG\0"
+ "CCS_RECEIVED_EARLY\0"
+ "CERTIFICATE_VERIFY_FAILED\0"
+ "CERT_CB_ERROR\0"
+ "CERT_LENGTH_MISMATCH\0"
+ "CHANNEL_ID_NOT_P256\0"
+ "CHANNEL_ID_SIGNATURE_INVALID\0"
+ "CIPHER_OR_HASH_UNAVAILABLE\0"
+ "CLIENTHELLO_PARSE_FAILED\0"
+ "CLIENTHELLO_TLSEXT\0"
+ "CONNECTION_REJECTED\0"
+ "CONNECTION_TYPE_NOT_SET\0"
+ "CUSTOM_EXTENSION_ERROR\0"
+ "DATA_LENGTH_TOO_LONG\0"
+ "DECRYPTION_FAILED\0"
+ "DECRYPTION_FAILED_OR_BAD_RECORD_MAC\0"
+ "DH_PUBLIC_VALUE_LENGTH_IS_WRONG\0"
+ "DH_P_TOO_LONG\0"
+ "DIGEST_CHECK_FAILED\0"
+ "DTLS_MESSAGE_TOO_BIG\0"
+ "ECC_CERT_NOT_FOR_SIGNING\0"
+ "EMS_STATE_INCONSISTENT\0"
+ "ENCRYPTED_LENGTH_TOO_LONG\0"
+ "ERROR_ADDING_EXTENSION\0"
+ "ERROR_IN_RECEIVED_CIPHER_LIST\0"
+ "ERROR_PARSING_EXTENSION\0"
+ "EXCESSIVE_MESSAGE_SIZE\0"
+ "EXTRA_DATA_IN_MESSAGE\0"
+ "FRAGMENT_MISMATCH\0"
+ "GOT_NEXT_PROTO_WITHOUT_EXTENSION\0"
+ "HANDSHAKE_FAILURE_ON_CLIENT_HELLO\0"
+ "HTTPS_PROXY_REQUEST\0"
+ "HTTP_REQUEST\0"
+ "INAPPROPRIATE_FALLBACK\0"
+ "INVALID_COMMAND\0"
+ "INVALID_MESSAGE\0"
+ "INVALID_SSL_SESSION\0"
+ "INVALID_TICKET_KEYS_LENGTH\0"
+ "LENGTH_MISMATCH\0"
+ "LIBRARY_HAS_NO_CIPHERS\0"
+ "MISSING_EXTENSION\0"
+ "MISSING_RSA_CERTIFICATE\0"
+ "MISSING_TMP_DH_KEY\0"
+ "MISSING_TMP_ECDH_KEY\0"
+ "MIXED_SPECIAL_OPERATOR_WITH_GROUPS\0"
+ "MTU_TOO_SMALL\0"
+ "NEGOTIATED_BOTH_NPN_AND_ALPN\0"
+ "NESTED_GROUP\0"
+ "NO_CERTIFICATES_RETURNED\0"
+ "NO_CERTIFICATE_ASSIGNED\0"
+ "NO_CERTIFICATE_SET\0"
+ "NO_CIPHERS_AVAILABLE\0"
+ "NO_CIPHERS_PASSED\0"
+ "NO_CIPHER_MATCH\0"
+ "NO_COMPRESSION_SPECIFIED\0"
+ "NO_METHOD_SPECIFIED\0"
+ "NO_P256_SUPPORT\0"
+ "NO_PRIVATE_KEY_ASSIGNED\0"
+ "NO_RENEGOTIATION\0"
+ "NO_REQUIRED_DIGEST\0"
+ "NO_SHARED_CIPHER\0"
+ "NULL_SSL_CTX\0"
+ "NULL_SSL_METHOD_PASSED\0"
+ "OLD_SESSION_CIPHER_NOT_RETURNED\0"
+ "OLD_SESSION_VERSION_NOT_RETURNED\0"
+ "PARSE_TLSEXT\0"
+ "PATH_TOO_LONG\0"
+ "PEER_DID_NOT_RETURN_A_CERTIFICATE\0"
+ "PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE\0"
+ "PROTOCOL_IS_SHUTDOWN\0"
+ "PSK_IDENTITY_NOT_FOUND\0"
+ "PSK_NO_CLIENT_CB\0"
+ "PSK_NO_SERVER_CB\0"
+ "READ_TIMEOUT_EXPIRED\0"
+ "RECORD_LENGTH_MISMATCH\0"
+ "RECORD_TOO_LARGE\0"
+ "RENEGOTIATION_ENCODING_ERR\0"
+ "RENEGOTIATION_MISMATCH\0"
+ "REQUIRED_CIPHER_MISSING\0"
+ "RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION\0"
+ "RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION\0"
+ "SCSV_RECEIVED_WHEN_RENEGOTIATING\0"
+ "SERVERHELLO_TLSEXT\0"
+ "SESSION_ID_CONTEXT_UNINITIALIZED\0"
+ "SESSION_MAY_NOT_BE_CREATED\0"
+ "SHUTDOWN_WHILE_IN_INIT\0"
+ "SIGNATURE_ALGORITHMS_EXTENSION_SENT_BY_SERVER\0"
+ "SRTP_COULD_NOT_ALLOCATE_PROFILES\0"
+ "SRTP_UNKNOWN_PROTECTION_PROFILE\0"
+ "SSL3_EXT_INVALID_SERVERNAME\0"
+ "SSLV3_ALERT_BAD_CERTIFICATE\0"
+ "SSLV3_ALERT_BAD_RECORD_MAC\0"
+ "SSLV3_ALERT_CERTIFICATE_EXPIRED\0"
+ "SSLV3_ALERT_CERTIFICATE_REVOKED\0"
+ "SSLV3_ALERT_CERTIFICATE_UNKNOWN\0"
+ "SSLV3_ALERT_CLOSE_NOTIFY\0"
+ "SSLV3_ALERT_DECOMPRESSION_FAILURE\0"
+ "SSLV3_ALERT_HANDSHAKE_FAILURE\0"
+ "SSLV3_ALERT_ILLEGAL_PARAMETER\0"
+ "SSLV3_ALERT_NO_CERTIFICATE\0"
+ "SSLV3_ALERT_UNEXPECTED_MESSAGE\0"
+ "SSLV3_ALERT_UNSUPPORTED_CERTIFICATE\0"
+ "SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION\0"
+ "SSL_HANDSHAKE_FAILURE\0"
+ "SSL_SESSION_ID_CONTEXT_TOO_LONG\0"
+ "TLSV1_ALERT_ACCESS_DENIED\0"
+ "TLSV1_ALERT_DECODE_ERROR\0"
+ "TLSV1_ALERT_DECRYPTION_FAILED\0"
+ "TLSV1_ALERT_DECRYPT_ERROR\0"
+ "TLSV1_ALERT_EXPORT_RESTRICTION\0"
+ "TLSV1_ALERT_INAPPROPRIATE_FALLBACK\0"
+ "TLSV1_ALERT_INSUFFICIENT_SECURITY\0"
+ "TLSV1_ALERT_INTERNAL_ERROR\0"
+ "TLSV1_ALERT_NO_RENEGOTIATION\0"
+ "TLSV1_ALERT_PROTOCOL_VERSION\0"
+ "TLSV1_ALERT_RECORD_OVERFLOW\0"
+ "TLSV1_ALERT_UNKNOWN_CA\0"
+ "TLSV1_ALERT_USER_CANCELLED\0"
+ "TLSV1_BAD_CERTIFICATE_HASH_VALUE\0"
+ "TLSV1_BAD_CERTIFICATE_STATUS_RESPONSE\0"
+ "TLSV1_CERTIFICATE_UNOBTAINABLE\0"
+ "TLSV1_UNRECOGNIZED_NAME\0"
+ "TLSV1_UNSUPPORTED_EXTENSION\0"
+ "TLS_PEER_DID_NOT_RESPOND_WITH_CERTIFICATE_LIST\0"
+ "TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG\0"
+ "TOO_MANY_EMPTY_FRAGMENTS\0"
+ "TOO_MANY_WARNING_ALERTS\0"
+ "UNABLE_TO_FIND_ECDH_PARAMETERS\0"
+ "UNEXPECTED_EXTENSION\0"
+ "UNEXPECTED_MESSAGE\0"
+ "UNEXPECTED_OPERATOR_IN_GROUP\0"
+ "UNEXPECTED_RECORD\0"
+ "UNKNOWN_ALERT_TYPE\0"
+ "UNKNOWN_CERTIFICATE_TYPE\0"
+ "UNKNOWN_CIPHER_RETURNED\0"
+ "UNKNOWN_CIPHER_TYPE\0"
+ "UNKNOWN_KEY_EXCHANGE_TYPE\0"
+ "UNKNOWN_PROTOCOL\0"
+ "UNKNOWN_SSL_VERSION\0"
+ "UNKNOWN_STATE\0"
+ "UNSAFE_LEGACY_RENEGOTIATION_DISABLED\0"
+ "UNSUPPORTED_COMPRESSION_ALGORITHM\0"
+ "UNSUPPORTED_ELLIPTIC_CURVE\0"
+ "UNSUPPORTED_PROTOCOL\0"
+ "WRONG_CERTIFICATE_TYPE\0"
+ "WRONG_CIPHER_RETURNED\0"
+ "WRONG_CURVE\0"
+ "WRONG_MESSAGE_TYPE\0"
+ "WRONG_SIGNATURE_TYPE\0"
+ "WRONG_SSL_VERSION\0"
+ "WRONG_VERSION_NUMBER\0"
+ "X509_LIB\0"
+ "X509_VERIFICATION_SETUP_PROBLEMS\0"
+ "AKID_MISMATCH\0"
+ "BAD_PKCS7_VERSION\0"
+ "BAD_X509_FILETYPE\0"
+ "BASE64_DECODE_ERROR\0"
+ "CANT_CHECK_DH_KEY\0"
+ "CERT_ALREADY_IN_HASH_TABLE\0"
+ "CRL_ALREADY_DELTA\0"
+ "CRL_VERIFY_FAILURE\0"
+ "IDP_MISMATCH\0"
+ "INVALID_DIRECTORY\0"
+ "INVALID_FIELD_NAME\0"
+ "INVALID_PSS_PARAMETERS\0"
+ "INVALID_TRUST\0"
+ "ISSUER_MISMATCH\0"
+ "KEY_TYPE_MISMATCH\0"
+ "KEY_VALUES_MISMATCH\0"
+ "LOADING_CERT_DIR\0"
+ "LOADING_DEFAULTS\0"
+ "NAME_TOO_LONG\0"
+ "NEWER_CRL_NOT_NEWER\0"
+ "NOT_PKCS7_SIGNED_DATA\0"
+ "NO_CERTIFICATES_INCLUDED\0"
+ "NO_CERT_SET_FOR_US_TO_VERIFY\0"
+ "NO_CRLS_INCLUDED\0"
+ "NO_CRL_NUMBER\0"
+ "PUBLIC_KEY_DECODE_ERROR\0"
+ "PUBLIC_KEY_ENCODE_ERROR\0"
+ "SHOULD_RETRY\0"
+ "UNKNOWN_KEY_TYPE\0"
+ "UNKNOWN_PURPOSE_ID\0"
+ "UNKNOWN_TRUST_ID\0"
+ "WRONG_LOOKUP_TYPE\0"
+ "BAD_IP_ADDRESS\0"
+ "BAD_OBJECT\0"
+ "BN_DEC2BN_ERROR\0"
+ "BN_TO_ASN1_INTEGER_ERROR\0"
+ "CANNOT_FIND_FREE_FUNCTION\0"
+ "DIRNAME_ERROR\0"
+ "DISTPOINT_ALREADY_SET\0"
+ "DUPLICATE_ZONE_ID\0"
+ "ERROR_CONVERTING_ZONE\0"
+ "ERROR_CREATING_EXTENSION\0"
+ "ERROR_IN_EXTENSION\0"
+ "EXPECTED_A_SECTION_NAME\0"
+ "EXTENSION_EXISTS\0"
+ "EXTENSION_NAME_ERROR\0"
+ "EXTENSION_NOT_FOUND\0"
+ "EXTENSION_SETTING_NOT_SUPPORTED\0"
+ "EXTENSION_VALUE_ERROR\0"
+ "ILLEGAL_EMPTY_EXTENSION\0"
+ "ILLEGAL_HEX_DIGIT\0"
+ "INCORRECT_POLICY_SYNTAX_TAG\0"
+ "INVALID_BOOLEAN_STRING\0"
+ "INVALID_EXTENSION_STRING\0"
+ "INVALID_MULTIPLE_RDNS\0"
+ "INVALID_NAME\0"
+ "INVALID_NULL_ARGUMENT\0"
+ "INVALID_NULL_NAME\0"
+ "INVALID_NULL_VALUE\0"
+ "INVALID_NUMBERS\0"
+ "INVALID_OBJECT_IDENTIFIER\0"
+ "INVALID_OPTION\0"
+ "INVALID_POLICY_IDENTIFIER\0"
+ "INVALID_PROXY_POLICY_SETTING\0"
+ "INVALID_PURPOSE\0"
+ "INVALID_SECTION\0"
+ "INVALID_SYNTAX\0"
+ "ISSUER_DECODE_ERROR\0"
+ "NEED_ORGANIZATION_AND_NUMBERS\0"
+ "NO_CONFIG_DATABASE\0"
+ "NO_ISSUER_CERTIFICATE\0"
+ "NO_ISSUER_DETAILS\0"
+ "NO_POLICY_IDENTIFIER\0"
+ "NO_PROXY_CERT_POLICY_LANGUAGE_DEFINED\0"
+ "NO_PUBLIC_KEY\0"
+ "NO_SUBJECT_DETAILS\0"
+ "ODD_NUMBER_OF_DIGITS\0"
+ "OPERATION_NOT_DEFINED\0"
+ "OTHERNAME_ERROR\0"
+ "POLICY_LANGUAGE_ALREADY_DEFINED\0"
+ "POLICY_PATH_LENGTH\0"
+ "POLICY_PATH_LENGTH_ALREADY_DEFINED\0"
+ "POLICY_WHEN_PROXY_LANGUAGE_REQUIRES_NO_POLICY\0"
+ "SECTION_NOT_FOUND\0"
+ "UNABLE_TO_GET_ISSUER_DETAILS\0"
+ "UNABLE_TO_GET_ISSUER_KEYID\0"
+ "UNKNOWN_BIT_STRING_ARGUMENT\0"
+ "UNKNOWN_EXTENSION\0"
+ "UNKNOWN_EXTENSION_NAME\0"
+ "UNKNOWN_OPTION\0"
+ "UNSUPPORTED_OPTION\0"
+ "USER_TOO_LONG\0"
+ "";
+