#ifndef SRC_CRYPTO_CRYPTO_KEYS_H_ #define SRC_CRYPTO_CRYPTO_KEYS_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "crypto/crypto_util.h" #include "base_object.h" #include "env.h" #include "memory_tracker.h" #include "node_buffer.h" #include "node_worker.h" #include "v8.h" #include #include #include namespace node { namespace crypto { enum PKEncodingType { // RSAPublicKey / RSAPrivateKey according to PKCS#1. kKeyEncodingPKCS1, // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8. kKeyEncodingPKCS8, // SubjectPublicKeyInfo according to X.509. kKeyEncodingSPKI, // ECPrivateKey according to SEC1. kKeyEncodingSEC1 }; enum PKFormatType { kKeyFormatDER, kKeyFormatPEM, kKeyFormatJWK }; enum KeyType { kKeyTypeSecret, kKeyTypePublic, kKeyTypePrivate }; enum KeyEncodingContext { kKeyContextInput, kKeyContextExport, kKeyContextGenerate }; enum class ParseKeyResult { kParseKeyOk, kParseKeyNotRecognized, kParseKeyNeedPassphrase, kParseKeyFailed }; struct AsymmetricKeyEncodingConfig { bool output_key_object_ = false; PKFormatType format_ = kKeyFormatDER; v8::Maybe type_ = v8::Nothing(); }; using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig; struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig { const EVP_CIPHER* cipher_; // The ByteSource alone is not enough to distinguish between "no passphrase" // and a zero-length passphrase (which can be a null pointer), therefore, we // use a NonCopyableMaybe. NonCopyableMaybe passphrase_; }; // This uses the built-in reference counter of OpenSSL to manage an EVP_PKEY // which is slightly more efficient than using a shared pointer and easier to // use. class ManagedEVPPKey : public MemoryRetainer { public: ManagedEVPPKey() : mutex_(std::make_shared()) {} explicit ManagedEVPPKey(EVPKeyPointer&& pkey); ManagedEVPPKey(const ManagedEVPPKey& that); ManagedEVPPKey& operator=(const ManagedEVPPKey& that); operator bool() const; EVP_PKEY* get() const; Mutex* mutex() const; void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(ManagedEVPPKey) SET_SELF_SIZE(ManagedEVPPKey) static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context); static NonCopyableMaybe GetPrivateKeyEncodingFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context); static ManagedEVPPKey GetParsedKey(Environment* env, EVPKeyPointer&& pkey, ParseKeyResult ret, const char* default_msg); static ManagedEVPPKey GetPublicOrPrivateKeyFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset); static ManagedEVPPKey GetPrivateKeyFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset, bool allow_key_object); static v8::Maybe ToEncodedPublicKey( Environment* env, ManagedEVPPKey key, const PublicKeyEncodingConfig& config, v8::Local* out); static v8::Maybe ToEncodedPrivateKey( Environment* env, ManagedEVPPKey key, const PrivateKeyEncodingConfig& config, v8::Local* out); private: size_t size_of_private_key() const; size_t size_of_public_key() const; EVPKeyPointer pkey_; std::shared_ptr mutex_; }; // Objects of this class can safely be shared among threads. class KeyObjectData : public MemoryRetainer { public: static std::shared_ptr CreateSecret(ByteSource key); static std::shared_ptr CreateAsymmetric( KeyType type, const ManagedEVPPKey& pkey); KeyType GetKeyType() const; // These functions allow unprotected access to the raw key material and should // only be used to implement cryptographic operations requiring the key. ManagedEVPPKey GetAsymmetricKey() const; const char* GetSymmetricKey() const; size_t GetSymmetricKeySize() const; void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(KeyObjectData) SET_SELF_SIZE(KeyObjectData) private: explicit KeyObjectData(ByteSource symmetric_key); KeyObjectData( KeyType type, const ManagedEVPPKey& pkey); const KeyType key_type_; const ByteSource symmetric_key_; const unsigned int symmetric_key_len_; const ManagedEVPPKey asymmetric_key_; }; class KeyObjectHandle : public BaseObject { public: static v8::Local Initialize(Environment* env); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); static v8::MaybeLocal Create(Environment* env, std::shared_ptr data); // TODO(tniessen): track the memory used by OpenSSL types SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(KeyObjectHandle) SET_SELF_SIZE(KeyObjectHandle) const std::shared_ptr& Data(); protected: static void New(const v8::FunctionCallbackInfo& args); static void Init(const v8::FunctionCallbackInfo& args); static void InitECRaw(const v8::FunctionCallbackInfo& args); static void InitEDRaw(const v8::FunctionCallbackInfo& args); static void InitJWK(const v8::FunctionCallbackInfo& args); static void GetKeyDetail(const v8::FunctionCallbackInfo& args); static void Equals(const v8::FunctionCallbackInfo& args); static void ExportJWK(const v8::FunctionCallbackInfo& args); static void GetAsymmetricKeyType( const v8::FunctionCallbackInfo& args); v8::Local GetAsymmetricKeyType() const; static void GetSymmetricKeySize( const v8::FunctionCallbackInfo& args); static void Export(const v8::FunctionCallbackInfo& args); v8::MaybeLocal ExportSecretKey() const; v8::MaybeLocal ExportPublicKey( const PublicKeyEncodingConfig& config) const; v8::MaybeLocal ExportPrivateKey( const PrivateKeyEncodingConfig& config) const; KeyObjectHandle(Environment* env, v8::Local wrap); private: std::shared_ptr data_; }; class NativeKeyObject : public BaseObject { public: static void Initialize(Environment* env, v8::Local target); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); static void New(const v8::FunctionCallbackInfo& args); static void CreateNativeKeyObjectClass( const v8::FunctionCallbackInfo& args); SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(NativeKeyObject) SET_SELF_SIZE(NativeKeyObject) class KeyObjectTransferData : public worker::TransferData { public: explicit KeyObjectTransferData(const std::shared_ptr& data) : data_(data) {} BaseObjectPtr Deserialize( Environment* env, v8::Local context, std::unique_ptr self) override; SET_MEMORY_INFO_NAME(KeyObjectTransferData) SET_SELF_SIZE(KeyObjectTransferData) SET_NO_MEMORY_INFO() private: std::shared_ptr data_; }; BaseObject::TransferMode GetTransferMode() const override; std::unique_ptr CloneForMessaging() const override; private: NativeKeyObject(Environment* env, v8::Local wrap, const std::shared_ptr& handle_data) : BaseObject(env, wrap), handle_data_(handle_data) { MakeWeak(); } std::shared_ptr handle_data_; }; enum WebCryptoKeyFormat { kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatSPKI, kWebCryptoKeyFormatJWK }; enum class WebCryptoKeyExportStatus { OK, INVALID_KEY_TYPE, FAILED }; template class KeyExportJob final : public CryptoJob { public: using AdditionalParams = typename KeyExportTraits::AdditionalParameters; static void New(const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args.IsConstructCall()); CryptoJobMode mode = GetCryptoJobMode(args[0]); CHECK(args[1]->IsUint32()); // Export Type CHECK(args[2]->IsObject()); // KeyObject WebCryptoKeyFormat format = static_cast(args[1].As()->Value()); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args[2]); CHECK_NOT_NULL(key); AdditionalParams params; if (KeyExportTraits::AdditionalConfig(args, 3, ¶ms).IsNothing()) { // The KeyExportTraits::AdditionalConfig is responsible for // calling an appropriate THROW_CRYPTO_* variant reporting // whatever error caused initialization to fail. return; } new KeyExportJob( env, args.This(), mode, key->Data(), format, std::move(params)); } static void Initialize( Environment* env, v8::Local target) { CryptoJob::Initialize(New, env, target); } static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { CryptoJob::RegisterExternalReferences(New, registry); } KeyExportJob( Environment* env, v8::Local object, CryptoJobMode mode, std::shared_ptr key, WebCryptoKeyFormat format, AdditionalParams&& params) : CryptoJob( env, object, AsyncWrap::PROVIDER_KEYEXPORTREQUEST, mode, std::move(params)), key_(key), format_(format) {} WebCryptoKeyFormat format() const { return format_; } void DoThreadPoolWork() override { const WebCryptoKeyExportStatus status = KeyExportTraits::DoExport( key_, format_, *CryptoJob::params(), &out_); if (status == WebCryptoKeyExportStatus::OK) { // Success! return; } CryptoErrorStore* errors = CryptoJob::errors(); errors->Capture(); if (errors->Empty()) { switch (status) { case WebCryptoKeyExportStatus::OK: UNREACHABLE(); break; case WebCryptoKeyExportStatus::INVALID_KEY_TYPE: errors->Insert(NodeCryptoError::INVALID_KEY_TYPE); break; case WebCryptoKeyExportStatus::FAILED: errors->Insert(NodeCryptoError::CIPHER_JOB_FAILED); break; } } } v8::Maybe ToResult( v8::Local* err, v8::Local* result) override { Environment* env = AsyncWrap::env(); CryptoErrorStore* errors = CryptoJob::errors(); if (out_.size() > 0) { CHECK(errors->Empty()); *err = v8::Undefined(env->isolate()); *result = out_.ToArrayBuffer(env); return v8::Just(!result->IsEmpty()); } if (errors->Empty()) errors->Capture(); CHECK(!errors->Empty()); *result = v8::Undefined(env->isolate()); return v8::Just(errors->ToException(env).ToLocal(err)); } SET_SELF_SIZE(KeyExportJob) void MemoryInfo(MemoryTracker* tracker) const override { tracker->TrackFieldWithSize("out", out_.size()); CryptoJob::MemoryInfo(tracker); } private: std::shared_ptr key_; WebCryptoKeyFormat format_; ByteSource out_; }; WebCryptoKeyExportStatus PKEY_SPKI_Export( KeyObjectData* key_data, ByteSource* out); WebCryptoKeyExportStatus PKEY_PKCS8_Export( KeyObjectData* key_data, ByteSource* out); namespace Keys { void Initialize(Environment* env, v8::Local target); void RegisterExternalReferences(ExternalReferenceRegistry* registry); } // namespace Keys } // namespace crypto } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_CRYPTO_CRYPTO_KEYS_H_