diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index 0d046be32b6361..35c15a7748ecc5 100644
--- a/doc/api/crypto.md
+++ b/doc/api/crypto.md
@@ -2659,6 +2659,35 @@ added: v10.0.0
 Enables the FIPS compliant crypto provider in a FIPS-enabled Node.js build.
 Throws an error if FIPS mode is not available.
 
+### crypto.sign(algorithm, data, key)
+<!-- YAML
+added: REPLACEME
+-->
+* `algorithm` {string | null | undefined}
+* `data` {Buffer | TypedArray | DataView}
+* `key` {Object | string | Buffer | KeyObject}
+* Returns: {Buffer}
+
+Calculates and returns the signature for `data` using the given private key and
+algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is
+dependent upon the key type (especially Ed25519 and Ed448).
+
+If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
+passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
+additional properties can be passed:
+
+* `padding`: {integer} - Optional padding value for RSA, one of the following:
+  * `crypto.constants.RSA_PKCS1_PADDING` (default)
+  * `crypto.constants.RSA_PKCS1_PSS_PADDING`
+
+  Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
+  used to sign the message as specified in section 3.1 of [RFC 4055][].
+* `saltLength`: {integer} - salt length for when padding is
+  `RSA_PKCS1_PSS_PADDING`. The special value
+  `crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
+  size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
+  maximum permissible value.
+
 ### crypto.timingSafeEqual(a, b)
 <!-- YAML
 added: v6.6.0
@@ -2680,6 +2709,41 @@ Use of `crypto.timingSafeEqual` does not guarantee that the *surrounding* code
 is timing-safe. Care should be taken to ensure that the surrounding code does
 not introduce timing vulnerabilities.
 
+### crypto.verify(algorithm, data, key, signature)
+<!-- YAML
+added: REPLACEME
+-->
+* `algorithm` {string | null | undefined}
+* `data` {Buffer | TypedArray | DataView}
+* `key` {Object | string | Buffer | KeyObject}
+* `signature` {Buffer | TypedArray | DataView}
+* Returns: {boolean}
+
+Verifies the given signature for `data` using the given key and algorithm. If
+`algorithm` is `null` or `undefined`, then the algorithm is dependent upon the
+key type (especially Ed25519 and Ed448).
+
+If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
+passed to [`crypto.createPublicKey()`][]. If it is an object, the following
+additional properties can be passed:
+
+* `padding`: {integer} - Optional padding value for RSA, one of the following:
+  * `crypto.constants.RSA_PKCS1_PADDING` (default)
+  * `crypto.constants.RSA_PKCS1_PSS_PADDING`
+
+  Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
+  used to sign the message as specified in section 3.1 of [RFC 4055][].
+* `saltLength`: {integer} - salt length for when padding is
+  `RSA_PKCS1_PSS_PADDING`. The special value
+  `crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
+  size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
+  maximum permissible value.
+
+The `signature` argument is the previously calculated signature for the `data`.
+
+Because public keys can be derived from private keys, a private key or a public
+key may be passed for `key`.
+
 ## Notes
 
 ### Legacy Streams API (pre Node.js v0.10)
diff --git a/lib/crypto.js b/lib/crypto.js
index 673a198466ec5c..e80c7a8327df98 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -80,7 +80,9 @@ const {
 } = require('internal/crypto/cipher');
 const {
   Sign,
-  Verify
+  signOneShot,
+  Verify,
+  verifyOneShot
 } = require('internal/crypto/sig');
 const {
   Hash,
@@ -174,12 +176,14 @@ module.exports = exports = {
   randomFillSync,
   scrypt,
   scryptSync,
+  sign: signOneShot,
   setEngine,
   timingSafeEqual,
   getFips: !fipsMode ? getFipsDisabled :
     fipsForced ? getFipsForced : getFipsCrypto,
   setFips: !fipsMode ? setFipsDisabled :
     fipsForced ? setFipsForced : setFipsCrypto,
+  verify: verifyOneShot,
 
   // Classes
   Certificate,
diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js
index ed3693d6fe90e8..9eacfec8c0b74a 100644
--- a/lib/internal/crypto/sig.js
+++ b/lib/internal/crypto/sig.js
@@ -2,10 +2,16 @@
 
 const {
   ERR_CRYPTO_SIGN_KEY_REQUIRED,
+  ERR_INVALID_ARG_TYPE,
   ERR_INVALID_OPT_VALUE
 } = require('internal/errors').codes;
 const { validateString } = require('internal/validators');
-const { Sign: _Sign, Verify: _Verify } = internalBinding('crypto');
+const {
+  Sign: _Sign,
+  Verify: _Verify,
+  signOneShot: _signOneShot,
+  verifyOneShot: _verifyOneShot
+} = internalBinding('crypto');
 const {
   RSA_PSS_SALTLEN_AUTO,
   RSA_PKCS1_PADDING
@@ -22,6 +28,7 @@ const {
   preparePublicOrPrivateKey
 } = require('internal/crypto/keys');
 const { Writable } = require('stream');
+const { isArrayBufferView } = require('internal/util/types');
 
 function Sign(algorithm, options) {
   if (!(this instanceof Sign))
@@ -91,6 +98,35 @@ Sign.prototype.sign = function sign(options, encoding) {
   return ret;
 };
 
+function signOneShot(algorithm, data, key) {
+  if (algorithm != null)
+    validateString(algorithm, 'algorithm');
+
+  if (!isArrayBufferView(data)) {
+    throw new ERR_INVALID_ARG_TYPE(
+      'data',
+      ['Buffer', 'TypedArray', 'DataView'],
+      data
+    );
+  }
+
+  if (!key)
+    throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
+
+  const {
+    data: keyData,
+    format: keyFormat,
+    type: keyType,
+    passphrase: keyPassphrase
+  } = preparePrivateKey(key);
+
+  // Options specific to RSA
+  const rsaPadding = getPadding(key);
+  const pssSaltLength = getSaltLength(key);
+
+  return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data,
+                      algorithm, rsaPadding, pssSaltLength);
+}
 
 function Verify(algorithm, options) {
   if (!(this instanceof Verify))
@@ -132,7 +168,44 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
 
 legacyNativeHandle(Verify);
 
+function verifyOneShot(algorithm, data, key, signature) {
+  if (algorithm != null)
+    validateString(algorithm, 'algorithm');
+
+  if (!isArrayBufferView(data)) {
+    throw new ERR_INVALID_ARG_TYPE(
+      'data',
+      ['Buffer', 'TypedArray', 'DataView'],
+      data
+    );
+  }
+
+  const {
+    data: keyData,
+    format: keyFormat,
+    type: keyType,
+    passphrase: keyPassphrase
+  } = preparePublicOrPrivateKey(key);
+
+  // Options specific to RSA
+  const rsaPadding = getPadding(key);
+  const pssSaltLength = getSaltLength(key);
+
+  if (!isArrayBufferView(signature)) {
+    throw new ERR_INVALID_ARG_TYPE(
+      'signature',
+      ['Buffer', 'TypedArray', 'DataView'],
+      signature
+    );
+  }
+
+  return _verifyOneShot(keyData, keyFormat, keyType, keyPassphrase, signature,
+                        data, algorithm, rsaPadding, pssSaltLength);
+}
+
 module.exports = {
   Sign,
-  Verify
+  signOneShot,
+  Verify,
+  verifyOneShot
 };
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 649ef9d5b4061e..9658c1d51a208b 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -4598,43 +4598,47 @@ SignBase::Error SignBase::Update(const char* data, int len) {
 }
 
 
-void SignBase::CheckThrow(SignBase::Error error) {
-  HandleScope scope(env()->isolate());
+void CheckThrow(Environment* env, SignBase::Error error) {
+  HandleScope scope(env->isolate());
 
   switch (error) {
-    case kSignUnknownDigest:
-      return env()->ThrowError("Unknown message digest");
+    case SignBase::Error::kSignUnknownDigest:
+      return env->ThrowError("Unknown message digest");
 
-    case kSignNotInitialised:
-      return env()->ThrowError("Not initialised");
+    case SignBase::Error::kSignNotInitialised:
+      return env->ThrowError("Not initialised");
 
-    case kSignInit:
-    case kSignUpdate:
-    case kSignPrivateKey:
-    case kSignPublicKey:
+    case SignBase::Error::kSignInit:
+    case SignBase::Error::kSignUpdate:
+    case SignBase::Error::kSignPrivateKey:
+    case SignBase::Error::kSignPublicKey:
       {
         unsigned long err = ERR_get_error();  // NOLINT(runtime/int)
         if (err)
-          return ThrowCryptoError(env(), err);
+          return ThrowCryptoError(env, err);
         switch (error) {
-          case kSignInit:
-            return env()->ThrowError("EVP_SignInit_ex failed");
-          case kSignUpdate:
-            return env()->ThrowError("EVP_SignUpdate failed");
-          case kSignPrivateKey:
-            return env()->ThrowError("PEM_read_bio_PrivateKey failed");
-          case kSignPublicKey:
-            return env()->ThrowError("PEM_read_bio_PUBKEY failed");
+          case SignBase::Error::kSignInit:
+            return env->ThrowError("EVP_SignInit_ex failed");
+          case SignBase::Error::kSignUpdate:
+            return env->ThrowError("EVP_SignUpdate failed");
+          case SignBase::Error::kSignPrivateKey:
+            return env->ThrowError("PEM_read_bio_PrivateKey failed");
+          case SignBase::Error::kSignPublicKey:
+            return env->ThrowError("PEM_read_bio_PUBKEY failed");
           default:
             ABORT();
         }
       }
 
-    case kSignOk:
+    case SignBase::Error::kSignOk:
       return;
   }
 }
 
+void SignBase::CheckThrow(SignBase::Error error) {
+  node::crypto::CheckThrow(env(), error);
+}
+
 static bool ApplyRSAOptions(const ManagedEVPPKey& pkey,
                             EVP_PKEY_CTX* pkctx,
                             int padding,
@@ -4800,6 +4804,90 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
   args.GetReturnValue().Set(ret.signature.ToBuffer().ToLocalChecked());
 }
 
+void SignOneShot(const FunctionCallbackInfo<Value>& args) {
+  ClearErrorOnReturn clear_error_on_return;
+  Environment* env = Environment::GetCurrent(args);
+
+  unsigned int offset = 0;
+  ManagedEVPPKey key = GetPrivateKeyFromJs(args, &offset, true);
+  if (!key)
+    return;
+
+#ifdef NODE_FIPS_MODE
+  /* Validate DSA2 parameters from FIPS 186-4 */
+  if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key.get())) {
+    DSA* dsa = EVP_PKEY_get0_DSA(key.get());
+    const BIGNUM* p;
+    DSA_get0_pqg(dsa, &p, nullptr, nullptr);
+    size_t L = BN_num_bits(p);
+    const BIGNUM* q;
+    DSA_get0_pqg(dsa, nullptr, &q, nullptr);
+    size_t N = BN_num_bits(q);
+    bool result = false;
+
+    if (L == 1024 && N == 160)
+      result = true;
+    else if (L == 2048 && N == 224)
+      result = true;
+    else if (L == 2048 && N == 256)
+      result = true;
+    else if (L == 3072 && N == 256)
+      result = true;
+
+    if (!result) {
+      return CheckThrow(env, SignBase::Error::kSignPrivateKey);
+    }
+  }
+#endif  // NODE_FIPS_MODE
+
+  ArrayBufferViewContents<char> data(args[offset]);
+
+  const EVP_MD* md;
+  if (args[offset + 1]->IsNullOrUndefined()) {
+    md = nullptr;
+  } else {
+    const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 1]);
+    md = EVP_get_digestbyname(*sign_type);
+    if (md == nullptr)
+      return CheckThrow(env, SignBase::Error::kSignUnknownDigest);
+  }
+
+  CHECK(args[offset + 2]->IsInt32());
+  int rsa_padding = args[offset + 2].As<Int32>()->Value();
+
+  CHECK(args[offset + 3]->IsInt32());
+  int rsa_salt_len = args[offset + 3].As<Int32>()->Value();
+
+  EVP_PKEY_CTX* pkctx = nullptr;
+  EVPMDPointer mdctx(EVP_MD_CTX_new());
+  if (!mdctx ||
+      !EVP_DigestSignInit(mdctx.get(), &pkctx, md, nullptr, key.get())) {
+    return CheckThrow(env, SignBase::Error::kSignInit);
+  }
+
+  if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
+    return CheckThrow(env, SignBase::Error::kSignPrivateKey);
+
+  const unsigned char* input =
+    reinterpret_cast<const unsigned char*>(data.data());
+  size_t sig_len;
+  if (!EVP_DigestSign(mdctx.get(), nullptr, &sig_len, input, data.length()))
+    return CheckThrow(env, SignBase::Error::kSignPrivateKey);
+
+  AllocatedBuffer signature = env->AllocateManaged(sig_len);
+  if (!EVP_DigestSign(mdctx.get(),
+                      reinterpret_cast<unsigned char*>(signature.data()),
+                      &sig_len,
+                      input,
+                      data.length())) {
+    return CheckThrow(env, SignBase::Error::kSignPrivateKey);
+  }
+
+  signature.Resize(sig_len);
+
+  args.GetReturnValue().Set(signature.ToBuffer().ToLocalChecked());
+}
+
 void Verify::Initialize(Environment* env, Local<Object> target) {
   Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
 
@@ -4904,6 +4992,66 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
   args.GetReturnValue().Set(verify_result);
 }
 
+void VerifyOneShot(const FunctionCallbackInfo<Value>& args) {
+  ClearErrorOnReturn clear_error_on_return;
+  Environment* env = Environment::GetCurrent(args);
+
+  unsigned int offset = 0;
+  ManagedEVPPKey key = GetPublicOrPrivateKeyFromJs(args, &offset);
+  if (!key)
+    return;
+
+  ArrayBufferViewContents<char> sig(args[offset]);
+
+  ArrayBufferViewContents<char> data(args[offset + 1]);
+
+  const EVP_MD* md;
+  if (args[offset + 2]->IsNullOrUndefined()) {
+    md = nullptr;
+  } else {
+    const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 2]);
+    md = EVP_get_digestbyname(*sign_type);
+    if (md == nullptr)
+      return CheckThrow(env, SignBase::Error::kSignUnknownDigest);
+  }
+
+  CHECK(args[offset + 3]->IsInt32());
+  int rsa_padding = args[offset + 3].As<Int32>()->Value();
+
+  CHECK(args[offset + 4]->IsInt32());
+  int rsa_salt_len = args[offset + 4].As<Int32>()->Value();
+
+  EVP_PKEY_CTX* pkctx = nullptr;
+  EVPMDPointer mdctx(EVP_MD_CTX_new());
+  if (!mdctx ||
+      !EVP_DigestVerifyInit(mdctx.get(), &pkctx, md, nullptr, key.get())) {
+    return CheckThrow(env, SignBase::Error::kSignInit);
+  }
+
+  if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
+    return CheckThrow(env, SignBase::Error::kSignPublicKey);
+
+  bool verify_result;
+  const int r = EVP_DigestVerify(
+    mdctx.get(),
+    reinterpret_cast<const unsigned char*>(sig.data()),
+    sig.length(),
+    reinterpret_cast<const unsigned char*>(data.data()),
+    data.length());
+  switch (r) {
+    case 1:
+      verify_result = true;
+      break;
+    case 0:
+      verify_result = false;
+      break;
+    default:
+      return CheckThrow(env, SignBase::Error::kSignPublicKey);
+  }
+
+  args.GetReturnValue().Set(verify_result);
+}
+
 template <PublicKeyCipher::Operation operation,
           PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
           PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
@@ -6577,6 +6725,8 @@ void Initialize(Local<Object> target,
   NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
   NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
   env->SetMethod(target, "randomBytes", RandomBytes);
+  env->SetMethod(target, "signOneShot", SignOneShot);
+  env->SetMethod(target, "verifyOneShot", VerifyOneShot);
   env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
   env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
   env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js
index e0b0d3fd7bf656..74de785c6453e0 100644
--- a/test/parallel/test-crypto-sign-verify.js
+++ b/test/parallel/test-crypto-sign-verify.js
@@ -142,63 +142,95 @@ common.expectsError(
     ];
     const errMessage = /^Error:.*data too large for key size$/;
 
+    const data = Buffer.from('Test123');
+
     signSaltLengths.forEach((signSaltLength) => {
       if (signSaltLength > max) {
         // If the salt length is too big, an Error should be thrown
         assert.throws(() => {
           crypto.createSign(algo)
-            .update('Test123')
+            .update(data)
             .sign({
               key: keyPem,
               padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
               saltLength: signSaltLength
             });
         }, errMessage);
+        assert.throws(() => {
+          crypto.sign(algo, data, {
+            key: keyPem,
+            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+            saltLength: signSaltLength
+          });
+        }, errMessage);
       } else {
         // Otherwise, a valid signature should be generated
         const s4 = crypto.createSign(algo)
-                         .update('Test123')
+                         .update(data)
                          .sign({
                            key: keyPem,
                            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
                            saltLength: signSaltLength
                          });
+        const s4_2 = crypto.sign(algo, data, {
+          key: keyPem,
+          padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+          saltLength: signSaltLength
+        });
 
-        let verified;
-        verifySaltLengths.forEach((verifySaltLength) => {
-          // Verification should succeed if and only if the salt length is
-          // correct
+        [s4, s4_2].forEach((sig) => {
+          let verified;
+          verifySaltLengths.forEach((verifySaltLength) => {
+            // Verification should succeed if and only if the salt length is
+            // correct
+            verified = crypto.createVerify(algo)
+                             .update(data)
+                             .verify({
+                               key: certPem,
+                               padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+                               saltLength: verifySaltLength
+                             }, sig);
+            assert.strictEqual(verified, crypto.verify(algo, data, {
+              key: certPem,
+              padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+              saltLength: verifySaltLength
+            }, sig));
+            const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) ===
+                                      getEffectiveSaltLength(verifySaltLength);
+            assert.strictEqual(verified, saltLengthCorrect);
+          });
+
+          // Verification using RSA_PSS_SALTLEN_AUTO should always work
           verified = crypto.createVerify(algo)
-                           .update('Test123')
+                           .update(data)
                            .verify({
                              key: certPem,
                              padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
-                             saltLength: verifySaltLength
-                           }, s4);
-          const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) ===
-                                    getEffectiveSaltLength(verifySaltLength);
-          assert.strictEqual(verified, saltLengthCorrect);
-        });
+                             saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
+                           }, sig);
+          assert.strictEqual(verified, true);
+          assert.strictEqual(verified, crypto.verify(algo, data, {
+            key: certPem,
+            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+            saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
+          }, sig));
 
-        // Verification using RSA_PSS_SALTLEN_AUTO should always work
-        verified = crypto.createVerify(algo)
-                         .update('Test123')
-                         .verify({
-                           key: certPem,
-                           padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
-                           saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
-                         }, s4);
-        assert.strictEqual(verified, true);
-
-        // Verifying an incorrect message should never work
-        verified = crypto.createVerify(algo)
-                         .update('Test1234')
-                         .verify({
-                           key: certPem,
-                           padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
-                           saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
-                         }, s4);
-        assert.strictEqual(verified, false);
+          // Verifying an incorrect message should never work
+          const wrongData = Buffer.from('Test1234');
+          verified = crypto.createVerify(algo)
+                           .update(wrongData)
+                           .verify({
+                             key: certPem,
+                             padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+                             saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
+                           }, sig);
+          assert.strictEqual(verified, false);
+          assert.strictEqual(verified, crypto.verify(algo, wrongData, {
+            key: certPem,
+            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+            saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
+          }, sig));
+        });
       }
     });
   }
@@ -281,40 +313,6 @@ common.expectsError(
   });
 }
 
-// RSA-PSS Sign test by verifying with 'openssl dgst -verify'
-{
-  if (!common.opensslCli)
-    common.skip('node compiled without OpenSSL CLI.');
-
-  const pubfile = fixtures.path('keys', 'rsa_public_2048.pem');
-  const privkey = fixtures.readKey('rsa_private_2048.pem');
-
-  const msg = 'Test123';
-  const s5 = crypto.createSign('SHA256')
-    .update(msg)
-    .sign({
-      key: privkey,
-      padding: crypto.constants.RSA_PKCS1_PSS_PADDING
-    });
-
-  const tmpdir = require('../common/tmpdir');
-  tmpdir.refresh();
-
-  const sigfile = path.join(tmpdir.path, 's5.sig');
-  fs.writeFileSync(sigfile, s5);
-  const msgfile = path.join(tmpdir.path, 's5.msg');
-  fs.writeFileSync(msgfile, msg);
-
-  const cmd =
-    `"${common.opensslCli}" dgst -sha256 -verify "${pubfile}" -signature "${
-      sigfile}" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "${
-      msgfile}"`;
-
-  exec(cmd, common.mustCall((err, stdout, stderr) => {
-    assert(stdout.includes('Verified OK'));
-  }));
-}
-
 {
   const sign = crypto.createSign('SHA1');
   const verify = crypto.createVerify('SHA1');
@@ -368,4 +366,144 @@ common.expectsError(
   assert.throws(
     () => crypto.createSign('sha8'),
     /Unknown message digest/);
+  assert.throws(
+    () => crypto.sign('sha8', Buffer.alloc(1), keyPem),
+    /Unknown message digest/);
+}
+
+[
+  { private: fixtures.readSync('test_ed25519_privkey.pem', 'ascii'),
+    public: fixtures.readSync('test_ed25519_pubkey.pem', 'ascii'),
+    algo: null,
+    sigLen: 64 },
+  { private: fixtures.readSync('test_ed448_privkey.pem', 'ascii'),
+    public: fixtures.readSync('test_ed448_pubkey.pem', 'ascii'),
+    algo: null,
+    sigLen: 114 },
+  { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'),
+    public: fixtures.readKey('rsa_public_2048.pem', 'ascii'),
+    algo: 'sha1',
+    sigLen: 256 }
+].forEach((pair) => {
+  const algo = pair.algo;
+
+  {
+    const data = Buffer.from('Hello world');
+    const sig = crypto.sign(algo, data, pair.private);
+    assert.strictEqual(sig.length, pair.sigLen);
+
+    assert.strictEqual(crypto.verify(algo, data, pair.private, sig),
+                       true);
+    assert.strictEqual(crypto.verify(algo, data, pair.public, sig),
+                       true);
+  }
+
+  {
+    const data = Buffer.from('Hello world');
+    const privKeyObj = crypto.createPrivateKey(pair.private);
+    const pubKeyObj = crypto.createPublicKey(pair.public);
+
+    const sig = crypto.sign(algo, data, privKeyObj);
+    assert.strictEqual(sig.length, pair.sigLen);
+
+    assert.strictEqual(crypto.verify(algo, data, privKeyObj, sig), true);
+    assert.strictEqual(crypto.verify(algo, data, pubKeyObj, sig), true);
+  }
+
+  {
+    const data = Buffer.from('Hello world');
+    const otherData = Buffer.from('Goodbye world');
+    const otherSig = crypto.sign(algo, otherData, pair.private);
+    assert.strictEqual(crypto.verify(algo, data, pair.private, otherSig),
+                       false);
+  }
+
+  [
+    Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array
+  ].forEach((clazz) => {
+    const data = new clazz();
+    const sig = crypto.sign(algo, data, pair.private);
+    assert.strictEqual(crypto.verify(algo, data, pair.private, sig),
+                       true);
+  });
+});
+
+[1, {}, [], true, Infinity].forEach((input) => {
+  const data = Buffer.alloc(1);
+  const sig = Buffer.alloc(1);
+  const type = typeof input;
+  const errObj = {
+    code: 'ERR_INVALID_ARG_TYPE',
+    name: 'TypeError',
+    message: 'The "data" argument must be one of type Buffer, ' +
+             `TypedArray, or DataView. Received type ${type}`
+  };
+
+  assert.throws(() => crypto.sign(null, input, 'asdf'), errObj);
+  assert.throws(() => crypto.verify(null, input, 'asdf', sig), errObj);
+
+  errObj.message = 'The "key" argument must be one of type string, Buffer, ' +
+                   `TypedArray, DataView, or KeyObject. Received type ${type}`;
+
+  assert.throws(() => crypto.sign(null, data, input), errObj);
+  assert.throws(() => crypto.verify(null, data, input, sig), errObj);
+
+  errObj.message = 'The "signature" argument must be one of type ' +
+                   `Buffer, TypedArray, or DataView. Received type ${type}`;
+  assert.throws(() => crypto.verify(null, data, 'test', input), errObj);
+});
+
+{
+  const privKey = fixtures.readKey('ec-key.pem');
+  const data = Buffer.from('Hello world');
+  [
+    crypto.createSign('sha1').update(data).sign(privKey),
+    crypto.sign('sha1', data, privKey)
+  ].forEach((sig) => {
+    // Signature length variability due to DER encoding
+    assert.strictEqual(sig.length >= 68, true);
+
+    assert.strictEqual(
+      crypto.createVerify('sha1').update(data).verify(privKey, sig),
+      true
+    );
+    assert.strictEqual(crypto.verify('sha1', data, privKey, sig), true);
+  });
+}
+
+
+// RSA-PSS Sign test by verifying with 'openssl dgst -verify'
+// Note: this particular test *must* be the last in this file as it will exit
+// early if no openssl binary is found
+{
+  if (!common.opensslCli)
+    common.skip('node compiled without OpenSSL CLI.');
+
+  const pubfile = fixtures.path('keys', 'rsa_public_2048.pem');
+  const privkey = fixtures.readKey('rsa_private_2048.pem');
+
+  const msg = 'Test123';
+  const s5 = crypto.createSign('SHA256')
+    .update(msg)
+    .sign({
+      key: privkey,
+      padding: crypto.constants.RSA_PKCS1_PSS_PADDING
+    });
+
+  const tmpdir = require('../common/tmpdir');
+  tmpdir.refresh();
+
+  const sigfile = path.join(tmpdir.path, 's5.sig');
+  fs.writeFileSync(sigfile, s5);
+  const msgfile = path.join(tmpdir.path, 's5.msg');
+  fs.writeFileSync(msgfile, msg);
+
+  const cmd =
+    `"${common.opensslCli}" dgst -sha256 -verify "${pubfile}" -signature "${
+      sigfile}" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "${
+      msgfile}"`;
+
+  exec(cmd, common.mustCall((err, stdout, stderr) => {
+    assert(stdout.includes('Verified OK'));
+  }));
 }