src: accept passphrase when crypto signing with private key
Previous behaviour was to drop to an openssl prompt ("Enter PEM pass phrase:") when supplying a private key with a passphrase. This change adds a fourth, optional, paramter that will be used as the passphrase. To include this parameter in a backwards compatible way it was necessary to expose the previously undocumented (and unexposed) feature of being able to explitly setting the output encoding.
This commit is contained in:
Родитель
8130744044
Коммит
f755ecf484
|
@ -290,8 +290,15 @@ with new data as it is streamed.
|
|||
### sign.sign(private_key, [output_format])
|
||||
|
||||
Calculates the signature on all the updated data passed through the
|
||||
sign. `private_key` is a string containing the PEM encoded private
|
||||
key for signing.
|
||||
sign.
|
||||
|
||||
`private_key` can be an object or a string. If `private_key` is a string, it is
|
||||
treated as the key with no passphrase.
|
||||
|
||||
`private_key`:
|
||||
|
||||
* `key` : A string holding the PEM encoded private key
|
||||
* `passphrase` : A string of passphrase for the private key
|
||||
|
||||
Returns the signature in `output_format` which can be `'binary'`,
|
||||
`'hex'` or `'base64'`. If no encoding is provided, then a buffer is
|
||||
|
|
|
@ -382,10 +382,15 @@ Sign.prototype._write = function(chunk, encoding, callback) {
|
|||
|
||||
Sign.prototype.update = Hash.prototype.update;
|
||||
|
||||
Sign.prototype.sign = function(key, encoding) {
|
||||
encoding = encoding || exports.DEFAULT_ENCODING;
|
||||
var ret = this._binding.sign(toBuf(key));
|
||||
Sign.prototype.sign = function(options, encoding) {
|
||||
if (!options)
|
||||
throw new Error('No key provided to sign');
|
||||
|
||||
var key = options.key || options;
|
||||
var passphrase = options.passphrase || null;
|
||||
var ret = this._binding.sign(toBuf(key), null, passphrase);
|
||||
|
||||
encoding = encoding || exports.DEFAULT_ENCODING;
|
||||
if (encoding && encoding !== 'buffer')
|
||||
ret = ret.toString(encoding);
|
||||
|
||||
|
|
|
@ -164,6 +164,19 @@ static void crypto_lock_cb(int mode, int n, const char* file, int line) {
|
|||
}
|
||||
|
||||
|
||||
static int CryptoPemCallback(char *buf, int size, int rwflag, void *u) {
|
||||
if (u) {
|
||||
size_t buflen = static_cast<size_t>(size);
|
||||
size_t len = strlen(static_cast<const char*>(u));
|
||||
len = len > buflen ? buflen : len;
|
||||
memcpy(buf, u, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ThrowCryptoErrorHelper(unsigned long err, bool is_type_error) {
|
||||
HandleScope scope(node_isolate);
|
||||
char errmsg[128];
|
||||
|
@ -342,7 +355,7 @@ static X509* LoadX509(Handle<Value> v) {
|
|||
if (!bio)
|
||||
return NULL;
|
||||
|
||||
X509 * x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
X509 * x509 = PEM_read_bio_X509(bio, NULL, CryptoPemCallback, NULL);
|
||||
if (!x509) {
|
||||
BIO_free_all(bio);
|
||||
return NULL;
|
||||
|
@ -372,7 +385,9 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
|||
|
||||
String::Utf8Value passphrase(args[1]);
|
||||
|
||||
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL,
|
||||
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio,
|
||||
NULL,
|
||||
CryptoPemCallback,
|
||||
len == 1 ? NULL : *passphrase);
|
||||
|
||||
if (!key) {
|
||||
|
@ -399,7 +414,7 @@ int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, BIO *in) {
|
|||
int ret = 0;
|
||||
X509 *x = NULL;
|
||||
|
||||
x = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
|
||||
x = PEM_read_bio_X509_AUX(in, NULL, CryptoPemCallback, NULL);
|
||||
|
||||
if (x == NULL) {
|
||||
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB);
|
||||
|
@ -425,7 +440,7 @@ int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, BIO *in) {
|
|||
ctx->extra_certs = NULL;
|
||||
}
|
||||
|
||||
while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
|
||||
while ((ca = PEM_read_bio_X509(in, NULL, CryptoPemCallback, NULL))) {
|
||||
r = SSL_CTX_add_extra_chain_cert(ctx, ca);
|
||||
|
||||
if (!r) {
|
||||
|
@ -530,7 +545,7 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
|||
if (!bio)
|
||||
return;
|
||||
|
||||
X509_CRL *x509 = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL);
|
||||
X509_CRL *x509 = PEM_read_bio_X509_CRL(bio, NULL, CryptoPemCallback, NULL);
|
||||
|
||||
if (x509 == NULL) {
|
||||
BIO_free_all(bio);
|
||||
|
@ -564,7 +579,7 @@ void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
|||
return;
|
||||
}
|
||||
|
||||
X509 *x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL);
|
||||
X509 *x509 = PEM_read_bio_X509(bp, NULL, CryptoPemCallback, NULL);
|
||||
|
||||
if (x509 == NULL) {
|
||||
BIO_free_all(bp);
|
||||
|
@ -2634,28 +2649,57 @@ void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
|
|||
}
|
||||
|
||||
|
||||
bool Sign::SignFinal(unsigned char** md_value,
|
||||
unsigned int *md_len,
|
||||
const char* key_pem,
|
||||
int key_pem_len) {
|
||||
if (!initialised_)
|
||||
bool Sign::SignFinal(const char* key_pem,
|
||||
int key_pem_len,
|
||||
const char* passphrase,
|
||||
unsigned char** sig,
|
||||
unsigned int *sig_len) {
|
||||
if (!initialised_) {
|
||||
ThrowError("Sign not initalised");
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* bp = NULL;
|
||||
EVP_PKEY* pkey = NULL;
|
||||
bool fatal = true;
|
||||
|
||||
bp = BIO_new(BIO_s_mem());
|
||||
if (bp == NULL)
|
||||
goto exit;
|
||||
|
||||
if (!BIO_write(bp, key_pem, key_pem_len))
|
||||
return false;
|
||||
goto exit;
|
||||
|
||||
pkey = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL);
|
||||
pkey = PEM_read_bio_PrivateKey(bp,
|
||||
NULL,
|
||||
CryptoPemCallback,
|
||||
const_cast<char*>(passphrase));
|
||||
if (pkey == NULL)
|
||||
return 0;
|
||||
goto exit;
|
||||
|
||||
if (EVP_SignFinal(&mdctx_, *sig, sig_len, pkey))
|
||||
fatal = false;
|
||||
|
||||
EVP_SignFinal(&mdctx_, *md_value, md_len, pkey);
|
||||
EVP_MD_CTX_cleanup(&mdctx_);
|
||||
initialised_ = false;
|
||||
EVP_PKEY_free(pkey);
|
||||
BIO_free_all(bp);
|
||||
|
||||
exit:
|
||||
if (pkey != NULL)
|
||||
EVP_PKEY_free(pkey);
|
||||
if (bp != NULL)
|
||||
BIO_free_all(bp);
|
||||
|
||||
EVP_MD_CTX_cleanup(&mdctx_);
|
||||
|
||||
if (fatal) {
|
||||
unsigned long err = ERR_get_error();
|
||||
if (err) {
|
||||
ThrowCryptoError(err);
|
||||
} else {
|
||||
ThrowError("PEM_read_bio_PrivateKey");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2668,19 +2712,26 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
|
|||
unsigned char* md_value;
|
||||
unsigned int md_len;
|
||||
|
||||
unsigned int len = args.Length();
|
||||
enum encoding encoding = BUFFER;
|
||||
if (args.Length() >= 2) {
|
||||
if (len >= 2 && args[1]->IsString()) {
|
||||
encoding = ParseEncoding(args[1]->ToString(), BUFFER);
|
||||
}
|
||||
|
||||
String::Utf8Value passphrase(args[2]);
|
||||
|
||||
ASSERT_IS_BUFFER(args[0]);
|
||||
ssize_t len = Buffer::Length(args[0]);
|
||||
size_t buf_len = Buffer::Length(args[0]);
|
||||
char* buf = Buffer::Data(args[0]);
|
||||
|
||||
md_len = 8192; // Maximum key size is 8192 bits
|
||||
md_value = new unsigned char[md_len];
|
||||
|
||||
bool r = sign->SignFinal(&md_value, &md_len, buf, len);
|
||||
bool r = sign->SignFinal(buf,
|
||||
buf_len,
|
||||
len >= 3 && !args[2]->IsNull() ? *passphrase : NULL,
|
||||
&md_value,
|
||||
&md_len);
|
||||
if (!r) {
|
||||
delete[] md_value;
|
||||
md_value = NULL;
|
||||
|
@ -2811,11 +2862,11 @@ bool Verify::VerifyFinal(const char* key_pem,
|
|||
// Split this out into a separate function once we have more than one
|
||||
// consumer of public keys.
|
||||
if (strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
|
||||
pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL);
|
||||
pkey = PEM_read_bio_PUBKEY(bp, NULL, CryptoPemCallback, NULL);
|
||||
if (pkey == NULL)
|
||||
goto exit;
|
||||
} else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
|
||||
RSA* rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
|
||||
RSA* rsa = PEM_read_bio_RSAPublicKey(bp, NULL, CryptoPemCallback, NULL);
|
||||
if (rsa) {
|
||||
pkey = EVP_PKEY_new();
|
||||
if (pkey)
|
||||
|
@ -2826,7 +2877,7 @@ bool Verify::VerifyFinal(const char* key_pem,
|
|||
goto exit;
|
||||
} else {
|
||||
// X.509 fallback
|
||||
x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL);
|
||||
x509 = PEM_read_bio_X509(bp, NULL, CryptoPemCallback, NULL);
|
||||
if (x509 == NULL)
|
||||
goto exit;
|
||||
|
||||
|
|
|
@ -434,10 +434,11 @@ class Sign : public WeakObject {
|
|||
|
||||
void SignInit(const char* sign_type);
|
||||
bool SignUpdate(const char* data, int len);
|
||||
bool SignFinal(unsigned char** md_value,
|
||||
unsigned int *md_len,
|
||||
const char* key_pem,
|
||||
int key_pem_len);
|
||||
bool SignFinal(const char* key_pem,
|
||||
int key_pem_len,
|
||||
const char* passphrase,
|
||||
unsigned char** sig,
|
||||
unsigned int *sig_len);
|
||||
|
||||
protected:
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN DSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,2E8DE7F5BD338C4118488E8D640FC695
|
||||
|
||||
7jnL7kBITpnfjHc4DiRF+9d0M9QKdQmiL9N7Bj52XC+L0jZTwfNld3xi6fQ1GNle
|
||||
RKrrgSgEYXxf+RJ9Nz/BOttYWnIyAWSswFIjm1vGjpoYTH6H/wFg1QSoZUfINO2I
|
||||
3p4Y+cYVWOgSAYegzT5sdWTKDJrRUUfYFThmdQk0uO3s8B7urQuVlEtHr02OuPAj
|
||||
hRiWaBtxkttBWA+x8dgpgAbjHlZIWv1fj0EAKaaVehaNzEyK+nyS1a816ssDV3t8
|
||||
YMOZdCdKgzbBr5T3Zf83hdzpmhagBNZve1P0kJEgGdydiRWSyTxotOt5AGsRSvza
|
||||
A9PVk8V/U6U1B18hACxGV4wiKCMQDAsHfo+BrVoZBvVBlpW4dfcsIEtQqwu18x2b
|
||||
wIW5Qc2zXFFL6P+eqfdZ0ZRdfsClX6/GYOO5Z4oy8iAQSuD1UdaG6Psy84U7LU8g
|
||||
++OcbEcw8UnKjKVJU+zBC4QmhxUSUiOQwcgeFQuMIEMtprUKztgm/oPClAMTY/pm
|
||||
FGxLZS59owVWkrN9Oc4ccw+6Zt6mDxH9cnHv+nkGlcK9pcD+gU1MVXUfuby+DNbI
|
||||
4iYqUoYZdb9gpWQ/VrXMX63NydXzE+vMB9BxOlgfw3b6BrFCUAuyH1FiIAlGeAjP
|
||||
LZa06WiOayeu6Lm8rzeu/Cjbe1pYzK9cyX3JxSGJxipPeO4URZ5+hyqBMyCCCUq8
|
||||
EVFcfwgkdQaeVeBUdxJyRXfuBQmlgJF0Ixlkw29StpI2dAbNrtcSAIwbsxDInK4b
|
||||
6ItdadW+0nCRAxdVbGt6oQIPqpjbmtVkqj+m1yAic1xYc7Kd2xngGdtOMefKefcw
|
||||
+7d7E82ljPycHDG2SNENsFV9TNENdNlaP1A1HQy+f/1YkLZHfNLQrUf1+BRR7oHI
|
||||
N0ACLF6jgZ9MFelB64774veUTLvcrmYKIX7TnV25kw28ZIQ8StmIt9YJ+Mq+x6DC
|
||||
32JbRBbwbHm598fCrfr471xw/SM1/OnPefVhJSQ6223IfjuSWG0Snvjo7mHbaduz
|
||||
xWW6ApT7/iilanZs8uKBuPaEtwu3CmJcdgj0tTUuXb5ivY3M0dD/ZLSQliqb49iU
|
||||
64LX0/kRvkUZ6nJqPA4nlKPfGebo6H0V4oX7XF/gm74=
|
||||
-----END DSA PRIVATE KEY-----
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,9D916E00476DFF9E70FA4BA9E3A6CB0E
|
||||
|
||||
oj0VC35ShSEqlfJ0rLGgkqJCyIK+mXSsa/X/xAur+lI/RVOVTWd7oQQGTdI/0rLX
|
||||
PdQR02Na3X9Rptezh6J04PfMGeFysxdT6RpC+rkHRPVbN0F4TqxSNNXzkwK70+EF
|
||||
dSuDMyVKv9YN4wWDf0g6VKe4ShAH/sqICQBrVyzWyYLvH/hwZmZZ1QEab6ylIKtb
|
||||
EJunwu9BxVVA04bbuATKkKjJOqDn0fG8hb4bYbyD02dJwgLePzzn36F31kcBCEHI
|
||||
tESlD3RsS+EtfpfgPkplXNOhqYzkD9auDb7Zy+ZwL20fjnJb75OSGu8gOg3KTljt
|
||||
mApZOg0nJ5Jk9ATAdyzyVSFOM1Hhcw12ws06Dq9KRnXgO6bbuadLTFRDdvSYDFvD
|
||||
ijUb+97UolQfYIXQMqXli3EIvHr7CTWe/3mpoDgK1mtr0+923Bm97XgE7KSr0L46
|
||||
n5QpNjCZf1vbXldNmW+TRifiJMgtVdS7x0N4vqDPNEe+FelVv3U4Pz3HIOtFuWLr
|
||||
ZCxlgVxJY4IsyYlV0ItQjIv8fJiAyemZdO2lA9K6h0eEF+9Apr3i79JGWUi74p5D
|
||||
Ooak4le0Va9O34f6FxCGn/a54A6bhKu24Ub/0gr/e4WRa7693euEdgIAZXhtMu2Z
|
||||
taU5SKjjXPzjmRCM2kINHTCENlaU4oFzTmj3TYY/jdKyNP1bHa07NhlomladkIHK
|
||||
GD6HaYkcbuwvh8hOPsopSwuS+NqjnGPq9Vv4ecBC+9veDEmpIE1iR6FK9Hjrre88
|
||||
kLoMQNmA+vuc8jG4/FIHM3SauQiR1ZJ6+zkz97kcmOf+X7LRaS4j6lfFR6qHiJ6y
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -46,6 +46,15 @@ var rsaPubPem = fs.readFileSync(common.fixturesDir + '/test_rsa_pubkey.pem',
|
|||
'ascii');
|
||||
var rsaKeyPem = fs.readFileSync(common.fixturesDir + '/test_rsa_privkey.pem',
|
||||
'ascii');
|
||||
var rsaKeyPemEncrypted = fs.readFileSync(
|
||||
common.fixturesDir + '/test_rsa_privkey_encrypted.pem', 'ascii');
|
||||
var dsaPubPem = fs.readFileSync(common.fixturesDir + '/test_dsa_pubkey.pem',
|
||||
'ascii');
|
||||
var dsaKeyPem = fs.readFileSync(common.fixturesDir + '/test_dsa_privkey.pem',
|
||||
'ascii');
|
||||
var dsaKeyPemEncrypted = fs.readFileSync(
|
||||
common.fixturesDir + '/test_dsa_privkey_encrypted.pem', 'ascii');
|
||||
|
||||
|
||||
try {
|
||||
var credentials = crypto.createCredentials(
|
||||
|
@ -761,6 +770,30 @@ assert.equal(rsaSignature,
|
|||
rsaVerify.update(rsaPubPem);
|
||||
assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true);
|
||||
|
||||
// Test RSA key signing/verification with encrypted key
|
||||
rsaSign = crypto.createSign('RSA-SHA1');
|
||||
rsaSign.update(rsaPubPem);
|
||||
assert.doesNotThrow(function() {
|
||||
var signOptions = { key: rsaKeyPemEncrypted, passphrase: 'password' };
|
||||
rsaSignature = rsaSign.sign(signOptions, 'hex');
|
||||
});
|
||||
assert.equal(rsaSignature,
|
||||
'5c50e3145c4e2497aadb0eabc83b342d0b0021ece0d4c4a064b7c' +
|
||||
'8f020d7e2688b122bfb54c724ac9ee169f83f66d2fe90abeb95e8' +
|
||||
'e1290e7e177152a4de3d944cf7d4883114a20ed0f78e70e25ef0f' +
|
||||
'60f06b858e6af42a2f276ede95bbc6bc9a9bbdda15bd663186a6f' +
|
||||
'40819a7af19e577bb2efa5e579a1f5ce8a0d4ca8b8f6');
|
||||
|
||||
rsaVerify = crypto.createVerify('RSA-SHA1');
|
||||
rsaVerify.update(rsaPubPem);
|
||||
assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true);
|
||||
|
||||
rsaSign = crypto.createSign('RSA-SHA1');
|
||||
rsaSign.update(rsaPubPem);
|
||||
assert.throws(function() {
|
||||
var signOptions = { key: rsaKeyPemEncrypted, passphrase: 'wrong' };
|
||||
rsaSign.sign(signOptions, 'hex');
|
||||
});
|
||||
|
||||
//
|
||||
// Test RSA signing and verification
|
||||
|
@ -798,24 +831,48 @@ assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true);
|
|||
// Test DSA signing and verification
|
||||
//
|
||||
(function() {
|
||||
var privateKey = fs.readFileSync(
|
||||
common.fixturesDir + '/test_dsa_privkey.pem');
|
||||
|
||||
var publicKey = fs.readFileSync(
|
||||
common.fixturesDir + '/test_dsa_pubkey.pem');
|
||||
|
||||
var input = 'I AM THE WALRUS';
|
||||
|
||||
// DSA signatures vary across runs so there is no static string to verify
|
||||
// against
|
||||
var sign = crypto.createSign('DSS1');
|
||||
sign.update(input);
|
||||
var signature = sign.sign(privateKey, 'hex');
|
||||
var signature = sign.sign(dsaKeyPem, 'hex');
|
||||
|
||||
var verify = crypto.createVerify('DSS1');
|
||||
verify.update(input);
|
||||
|
||||
assert.strictEqual(verify.verify(publicKey, signature, 'hex'), true);
|
||||
assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true);
|
||||
})();
|
||||
|
||||
|
||||
//
|
||||
// Test DSA signing and verification with encrypted key
|
||||
//
|
||||
(function() {
|
||||
var input = 'I AM THE WALRUS';
|
||||
|
||||
var sign = crypto.createSign('DSS1');
|
||||
sign.update(input);
|
||||
assert.throws(function() {
|
||||
sign.sign({ key: dsaKeyPemEncrypted, passphrase: 'wrong' }, 'hex');
|
||||
});
|
||||
|
||||
// DSA signatures vary across runs so there is no static string to verify
|
||||
// against
|
||||
var sign = crypto.createSign('DSS1');
|
||||
sign.update(input);
|
||||
|
||||
var signature;
|
||||
assert.doesNotThrow(function() {
|
||||
var signOptions = { key: dsaKeyPemEncrypted, passphrase: 'password' };
|
||||
signature = sign.sign(signOptions, 'hex');
|
||||
});
|
||||
|
||||
var verify = crypto.createVerify('DSS1');
|
||||
verify.update(input);
|
||||
|
||||
assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true);
|
||||
})();
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче