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:
Thom Seddon 2013-10-04 12:59:38 +01:00 коммит произвёл Fedor Indutny
Родитель 8130744044
Коммит f755ecf484
7 изменённых файлов: 203 добавлений и 41 удалений

Просмотреть файл

@ -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);

23
test/fixtures/test_dsa_privkey_encrypted.pem поставляемый Normal file
Просмотреть файл

@ -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-----

18
test/fixtures/test_rsa_privkey_encrypted.pem поставляемый Normal file
Просмотреть файл

@ -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);
})();