updates for greenlock-express

This commit is contained in:
AJ ONeal 2019-10-27 04:38:05 -06:00
parent 7fc11f7908
commit 7774d0f8b1
7 changed files with 247 additions and 132 deletions

View File

@ -65,12 +65,23 @@ var gl = Greenlock.create({
});
```
| Parameter | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| maintainerEmail | the developer contact for critical bug and security notifications |
| maintainerUpdates | (default: false) receive occasional non-critical notifications |
| subscriberEmail | the contact who agrees to the Let's Encrypt Subscriber Agreement and the Greenlock Terms of Service<br>this contact receives renewal failure notifications |
| agreeToTerms | (default: false) either 'true' or a function that presents the Terms of Service and returns it once accepted |
| Parameter | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| servername | the default servername to use for non-sni requests (many IoT clients) |
| maintainerEmail | the developer contact for critical bug and security notifications |
| maintainerUpdates | (default: false) receive occasional non-critical notifications |
| maintainerPackage | if you publish your package for others to use, `require('./package.json').name` here |
| maintainerPackageVersion | if you publish your package for others to use, `require('./package.json').version` here |
| subscriberEmail | the contact who agrees to the Let's Encrypt Subscriber Agreement and the Greenlock Terms of Service<br>this contact receives renewal failure notifications |
| agreeToTerms | (default: false) either 'true' or a function that presents the Terms of Service and returns it once accepted |
| store | override the default storage module |
| store.module | the name of your storage module |
| store.xxxx | options specific to your storage module |
| challenges['http-01'] | provide an http-01 challenge module |
| challenges['dns-01'] | provide a dns-01 challenge module |
| challenges['tls-alpn-01'] | provide a tls-alpn-01 challenge module |
| challenges[type].module | the name of your challenge module |
| challenges[type].xxxx | module-specific options |
### Add Approved Domains
@ -130,15 +141,18 @@ TODO
</details>
<!--
<details>
<summary>Node.js</summary>
```bash
npm install --save @root/greenlock
npm install --save greenlock-manager-fs
npm install --save greenlock-store-fs
npm install --save acme-http-01-standalone
```
<!--
TODO
</details>

View File

@ -6,8 +6,11 @@ var E = require('./errors.js');
var pending = {};
A._getOrCreate = function(greenlock, db, acme, args) {
var email = args.subscriberEmail || greenlock._defaults.subscriberEmail;
A._getOrCreate = function(gnlck, mconf, db, acme, args) {
var email =
args.subscriberEmail ||
mconf.subscriberEmail ||
gnlck._defaults.subscriberEmail;
if (!email) {
throw E.NO_SUBSCRIBER('get account', args.subject);
@ -23,7 +26,14 @@ A._getOrCreate = function(greenlock, db, acme, args) {
return pending[email];
}
pending[email] = A._rawGetOrCreate(greenlock, db, acme, args, email)
pending[email] = A._rawGetOrCreate(
gnlck,
mconf,
db,
acme,
args,
email
)
.catch(function(e) {
delete pending[email];
throw e;
@ -38,32 +48,37 @@ A._getOrCreate = function(greenlock, db, acme, args) {
};
// What we really need out of this is the private key and the ACME "key" id
A._rawGetOrCreate = function(greenlock, db, acme, args, email) {
A._rawGetOrCreate = function(gnlck, mconf, db, acme, args, email) {
var p;
if (db.check) {
p = A._checkStore(greenlock, db, acme, args, email);
p = A._checkStore(gnlck, mconf, db, acme, args, email);
} else {
p = Promise.resolve(null);
}
return p.then(function(fullAccount) {
if (!fullAccount) {
return A._newAccount(greenlock, db, acme, args, email, null);
return A._newAccount(gnlck, mconf, db, acme, args, email, null);
}
if (fullAccount.keypair && fullAccount.key && fullAccount.key.kid) {
return fullAccount;
}
return A._newAccount(greenlock, db, acme, args, email, fullAccount);
return A._newAccount(gnlck, mconf, db, acme, args, email, fullAccount);
});
};
A._newAccount = function(greenlock, db, acme, args, email, fullAccount) {
var keyType = args.accountKeyType || greenlock._defaults.accountKeyType;
A._newAccount = function(gnlck, mconf, db, acme, args, email, fullAccount) {
var keyType =
args.accountKeyType ||
mconf.accountKeyType ||
gnlck._defaults.accountKeyType;
var query = {
subject: args.subject,
email: email,
subscriberEmail: email,
customerEmail: args.customerEmail,
account: fullAccount || {}
};
@ -73,8 +88,10 @@ A._newAccount = function(greenlock, db, acme, args, email, fullAccount) {
var accReg = {
subscriberEmail: email,
agreeToTerms:
args.agreeToTerms || greenlock._defaults.agreeToTerms,
accountKeypair: keypair,
args.agreeToTerms ||
mconf.agreeToTerms ||
gnlck._defaults.agreeToTerms,
accountKey: keypair.privateKeyJwk || keypair.private,
debug: args.debug
};
return acme.accounts.create(accReg).then(function(receipt) {
@ -86,7 +103,9 @@ A._newAccount = function(greenlock, db, acme, args, email, fullAccount) {
receipt &&
receipt.key &&
(receipt.key.kid || receipt.kid),
email: args.email
email: args.email,
subscriberEmail: email,
customerEmail: args.customerEmail
};
var keyP;
@ -95,6 +114,13 @@ A._newAccount = function(greenlock, db, acme, args, email, fullAccount) {
} else {
query.keypair = keypair;
query.receipt = receipt;
query.directoryUrl = gnlck._defaults.directoryUrl;
/*
query.server = gnlck._defaults.directoryUrl.replace(
/^https?:\/\//i,
''
);
*/
keyP = db.setKeypair(query, keypair);
}
@ -109,7 +135,16 @@ A._newAccount = function(greenlock, db, acme, args, email, fullAccount) {
{
// id to be set by Store
email: email,
agreeTos: true
subscriberEmail: email,
customerEmail: args.customerEmail,
agreeTos: true,
directoryUrl: gnlck._defaults.directoryUrl
/*
server: gnlck._defaults.directoryUrl.replace(
/^https?:\/\//i,
''
)
*/
},
reg
);
@ -137,7 +172,7 @@ A._newAccount = function(greenlock, db, acme, args, email, fullAccount) {
);
};
A._checkStore = function(greenlock, db, acme, args, email) {
A._checkStore = function(gnlck, mconf, db, acme, args, email) {
if ((args.domain || args.domains) && !args.subject) {
console.warn("use 'subject' instead of 'domain'");
args.subject = args.domain;
@ -148,12 +183,12 @@ A._checkStore = function(greenlock, db, acme, args, email) {
account = {};
}
if (args.accountKeypair) {
if (args.accountKey) {
console.warn(
'rather than passing accountKeypair, put it directly into your account key store'
'rather than passing accountKey, put it directly into your account key store'
);
// TODO we probably don't need this
return U._importKeypair(args.accountKeypair);
return U._importKeypair(args.accountKey);
}
if (!db.check) {
@ -165,6 +200,8 @@ A._checkStore = function(greenlock, db, acme, args, email) {
//keypair: undefined,
//receipt: undefined,
email: email,
subscriberEmail: email,
customerEmail: args.customerEmail || mconf.customerEmail,
account: account
})
.then(function(fullAccount) {

View File

@ -4,13 +4,27 @@ var C = module.exports;
var U = require('./utils.js');
var CSR = require('@root/csr');
var Enc = require('@root/encoding');
var Keypairs = require('@root/keypairs');
var pending = {};
var rawPending = {};
// What the abbreviations mean
//
// gnlkc => greenlock
// mconf => manager config
// db => greenlock store instance
// acme => instance of ACME.js
// chs => instances of challenges
// acc => account
// args => site / extra options
// Certificates
C._getOrOrder = function(greenlock, db, acme, challenges, account, args) {
var email = args.subscriberEmail || greenlock._defaults.subscriberEmail;
C._getOrOrder = function(gnlck, mconf, db, acme, chs, acc, args) {
var email =
args.subscriberEmail ||
mconf.subscriberEmail ||
gnlck._defaults.subscriberEmail;
var id = args.altnames.join(' ');
if (pending[id]) {
@ -18,11 +32,12 @@ C._getOrOrder = function(greenlock, db, acme, challenges, account, args) {
}
pending[id] = C._rawGetOrOrder(
greenlock,
gnlck,
mconf,
db,
acme,
challenges,
account,
chs,
acc,
email,
args
)
@ -39,33 +54,26 @@ C._getOrOrder = function(greenlock, db, acme, challenges, account, args) {
};
// Certificates
C._rawGetOrOrder = function(
greenlock,
db,
acme,
challenges,
account,
email,
args
) {
return C._check(db, args).then(function(pems) {
C._rawGetOrOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) {
return C._check(gnlck, mconf, db, args).then(function(pems) {
// No pems? get some!
if (!pems) {
return C._rawOrder(
greenlock,
gnlck,
mconf,
db,
acme,
challenges,
account,
chs,
acc,
email,
args
).then(function(newPems) {
// do not wait on notify
greenlock._notify('cert_issue', {
gnlck._notify('cert_issue', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: account,
account: acc,
email: email,
pems: newPems
});
@ -74,7 +82,7 @@ C._rawGetOrOrder = function(
}
// Nice and fresh? We're done!
if (!C._isStale(greenlock, args, pems)) {
if (!C._isStale(gnlck, mconf, args, pems)) {
// return existing unexpired (although potentially stale) certificates when available
// there will be an additional .renewing property if the certs are being asynchronously renewed
//pems._type = 'current';
@ -82,26 +90,20 @@ C._rawGetOrOrder = function(
}
// Getting stale? Let's renew to freshen up!
var p = C._rawOrder(
greenlock,
db,
acme,
challenges,
account,
email,
args
).then(function(renewedPems) {
// do not wait on notify
greenlock._notify('cert_renewal', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: account,
email: email,
pems: renewedPems
});
return renewedPems;
});
var p = C._rawOrder(gnlck, mconf, db, acme, chs, acc, email, args).then(
function(renewedPems) {
// do not wait on notify
gnlck._notify('cert_renewal', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: acc,
email: email,
pems: renewedPems
});
return renewedPems;
}
);
// TODO what should this be?
if (args.waitForRenewal) {
@ -114,7 +116,7 @@ C._rawGetOrOrder = function(
// we have another promise here because it the optional renewal
// may resolve in a different stack than the returned pems
C._rawOrder = function(greenlock, db, acme, challenges, account, email, args) {
C._rawOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) {
var id = args.altnames
.slice(0)
.sort()
@ -123,10 +125,17 @@ C._rawOrder = function(greenlock, db, acme, challenges, account, email, args) {
return rawPending[id];
}
var keyType = args.serverKeyType || greenlock._defaults.serverKeyType;
var keyType =
args.serverKeyType ||
mconf.serverKeyType ||
gnlck._defaults.serverKeyType;
var query = {
subject: args.subject,
certificate: args.certificate || {}
certificate: args.certificate || {},
directoryUrl:
args.directoryUrl ||
mconf.directoryUrl ||
gnlck._defaults.directoryUrl
};
rawPending[id] = U._getOrCreateKeypair(db, args.subject, query, keyType)
.then(function(kresult) {
@ -134,7 +143,7 @@ C._rawOrder = function(greenlock, db, acme, challenges, account, email, args) {
var domains = args.altnames.slice(0);
return CSR.csr({
jwk: serverKeypair.privateKeyJwk,
jwk: serverKeypair.privateKeyJwk || serverKeypair.private,
domains: domains,
encoding: 'der'
})
@ -144,21 +153,22 @@ C._rawOrder = function(greenlock, db, acme, challenges, account, email, args) {
})
.then(function(csr) {
function notify() {
greenlock._notify('challenge_status', {
gnlck._notify('challenge_status', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: account,
account: acc,
email: email
});
}
var certReq = {
debug: args.debug || greenlock._defaults.debug,
debug: args.debug || gnlck._defaults.debug,
challenges: challenges,
account: account, // only used if accounts.key.kid exists
accountKeypair: account.keypair,
keypair: account.keypair, // TODO
challenges: chs,
account: acc, // only used if accounts.key.kid exists
accountKey:
acc.keypair.privateKeyJwk || acc.keypair.private,
keypair: acc.keypair, // TODO
csr: csr,
domains: domains, // because ACME.js v3 uses `domains` still, actually
onChallengeStatus: notify,
@ -190,7 +200,7 @@ C._rawOrder = function(greenlock, db, acme, challenges, account, email, args) {
return db.set(query);
})
.then(function() {
return C._check(db, args);
return C._check(gnlck, mconf, db, args);
})
.then(function(bundle) {
// TODO notify Manager
@ -207,14 +217,17 @@ C._rawOrder = function(greenlock, db, acme, challenges, account, email, args) {
};
// returns pems, if they exist
C._check = function(db, args) {
C._check = function(gnlck, mconf, db, args) {
var query = {
subject: args.subject,
// may contain certificate.id
certificate: args.certificate
certificate: args.certificate,
directoryUrl:
args.directoryUrl ||
mconf.directoryUrl ||
gnlck._defaults.directoryUrl
};
return db.check(query).then(function(pems) {
console.log('[debug] has pems? (yes)', pems);
if (!pems) {
return null;
}
@ -235,9 +248,13 @@ C._check = function(db, args) {
return U._getKeypair(db, args.subject, query)
.then(function(keypair) {
console.log('[debug get keypair]', Object.keys(keypair));
pems.privkey = keypair.privateKeyPem;
return pems;
return Keypairs.export({
jwk: keypair.privateKeyJwk || keypair.private,
encoding: 'pem'
}).then(function(pem) {
pems.privkey = pem;
return pems;
});
})
.catch(function() {
// TODO report error, but continue the process as with no cert
@ -247,12 +264,12 @@ C._check = function(db, args) {
};
// Certificates
C._isStale = function(greenlock, args, pems) {
C._isStale = function(gnlck, mconf, args, pems) {
if (args.duplicate) {
return true;
}
var renewAt = C._renewableAt(greenlock, args, pems);
var renewAt = C._renewableAt(gnlck, mconf, args, pems);
if (Date.now() >= renewAt) {
return true;
@ -261,12 +278,16 @@ C._isStale = function(greenlock, args, pems) {
return false;
};
C._renewableAt = function(greenlock, args, pems) {
C._renewableAt = function(gnlck, mconf, args, pems) {
if (args.renewAt) {
return args.renewAt;
}
var renewOffset = args.renewOffset || greenlock._defaults.renewOffset || 0;
var renewOffset =
args.renewOffset ||
mconf.renewOffset ||
gnlck._defaults.renewOffset ||
0;
var week = 1000 * 60 * 60 * 24 * 6;
if (!args.force && Math.abs(renewOffset) < week) {
throw new Error(

View File

@ -65,10 +65,37 @@ G.create = function(gconf) {
var defaults = G._defaults(gconf);
greenlock.manager = Manager.create(defaults);
greenlock._init = function() {
var p;
greenlock._init = function() {
return p;
};
p = greenlock.manager.config().then(function(conf) {
var changed = false;
if (!conf.challenges) {
changed = true;
conf.challenges = defaults.challenges;
}
if (!conf.store) {
changed = true;
conf.store = defaults.store;
}
if (changed) {
return greenlock.manager.config(conf);
}
});
return p;
};
greenlock._init();
// The goal here is to reduce boilerplate, such as error checking
// and duration parsing, that a manager must implement
greenlock.add = function(args) {
return greenlock._init().then(function() {
return greenlock._add(args);
});
};
greenlock._add = function(args) {
return Promise.resolve().then(function() {
// durations
if (args.renewOffset) {
@ -128,23 +155,26 @@ G.create = function(gconf) {
greenlock._notify = function(ev, params) {
var mng = greenlock.manager;
if (mng.notif || greenlock._defaults.notify) {
if (mng.notify || greenlock._defaults.notify) {
try {
var p = (mng.notify || greenlock._defaults.notify)(ev, params);
if (p && p.catch) {
p.catch(function(e) {
console.error("Error on event '" + ev + "':");
console.error(
"Promise Rejection on event '" + ev + "':"
);
console.error(e);
});
}
} catch (e) {
console.error("Error on event '" + ev + "':");
console.error("Thrown Exception on event '" + ev + "':");
console.error(e);
}
} else {
if (/error/i.test(ev)) {
console.error("Error event '" + ev + "':");
console.error(params);
console.error(params.stack);
}
}
/*
@ -180,6 +210,11 @@ G.create = function(gconf) {
// needs to get info about the renewal, such as which store and challenge(s) to use
greenlock.renew = function(args) {
return greenlock.manager.config().then(function(mconf) {
return greenlock._renew(mconf, args);
});
};
greenlock._renew = function(mconf, args) {
if (!args) {
args = {};
}
@ -207,7 +242,7 @@ G.create = function(gconf) {
function next() {
var site = sites.shift();
if (!site) {
return null;
return Promise.resolve(null);
}
var order = {
@ -216,7 +251,7 @@ G.create = function(gconf) {
renewedOrFailed.push(order);
// TODO merge args + result?
return greenlock
.order(site)
._order(mconf, site)
.then(function(pems) {
order.pems = pems;
})
@ -237,7 +272,10 @@ G.create = function(gconf) {
greenlock._acme = function(args) {
var acme = ACME.create({
debug: args.debug
maintainerEmail: greenlock._defaults.maintainerEmail,
packageAgent: greenlock._defaults.packageAgent,
notify: greenlock._notify,
debug: greenlock._defaults.debug || args.debug
});
var dirUrl = args.directoryUrl || greenlock._defaults.directoryUrl;
@ -266,6 +304,13 @@ G.create = function(gconf) {
};
greenlock.order = function(args) {
return greenlock._init().then(function() {
return greenlock.manager.config().then(function(mconf) {
return greenlock._order(mconf, args);
});
});
};
greenlock._order = function(mconf, args) {
return greenlock._acme(args).then(function(acme) {
var storeConf = args.store || greenlock._defaults.store;
return P._load(storeConf.module).then(function(plugin) {
@ -276,17 +321,18 @@ G.create = function(gconf) {
return A._getOrCreate(
greenlock,
mconf,
store.accounts,
acme,
args
).then(function(account) {
var challengeConfs =
args.challenges || greenlock._defaults.challenges;
console.log('[debug] challenge confs', challengeConfs);
args.challenges ||
mconf.challenges ||
greenlock._defaults.challenges;
return Promise.all(
Object.keys(challengeConfs).map(function(typ01) {
var chConf = challengeConfs[typ01];
console.log('[debug] module', chConf);
return P._load(chConf.module).then(function(
plugin
) {
@ -305,6 +351,7 @@ G.create = function(gconf) {
});
return C._getOrOrder(
greenlock,
mconf,
store.certificates,
acme,
challenges,

34
package-lock.json generated
View File

@ -5,38 +5,15 @@
"requires": true,
"dependencies": {
"@root/acme": {
"version": "3.0.0-wip.3",
"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.0-wip.3.tgz",
"integrity": "sha512-7Fq9FuO0WQgKPgyYmKHst71EbIqH764A3j6vF1aKemgWXXq2Wqy8G+2SJwt3/MSXhQ7X+qLmWRLLJ7U4Zlygsg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.5.tgz",
"integrity": "sha512-qtAE7E0yPlajlhiojT6Fz8PV0BIvq+eNKY1mbG3zFf+idppYn66R7nrn17mvrXsQaRhabZ/C+M9MAk4Xt8UBBA==",
"requires": {
"@root/csr": "^0.8.1",
"@root/encoding": "^1.0.1",
"@root/keypairs": "^0.9.0",
"@root/pem": "^1.0.4",
"@root/request": "^1.3.11",
"@root/x509": "^0.7.2"
},
"dependencies": {
"@root/csr": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
"requires": {
"@root/asn1": "^1.0.0",
"@root/pem": "^1.0.4",
"@root/x509": "^0.7.2"
}
},
"@root/keypairs": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
"requires": {
"@root/encoding": "^1.0.1",
"@root/pem": "^1.0.4",
"@root/x509": "^0.7.2"
}
}
}
},
"@root/asn1": {
@ -117,6 +94,11 @@
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
"dev": true
},
"greenlock-manager-fs": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-0.1.0.tgz",
"integrity": "sha512-tEt9npVDDR27FThVsh2PizkNPLS9V60ljbxpYnLRUkv5LoMvOTm4hHfNCEanOIL5PY8rfilh96+8v519xeYKQg=="
},
"greenlock-store-fs": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.0.2.tgz",

View File

@ -34,13 +34,15 @@
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0",
"dependencies": {
"@root/acme": "^3.0.0-wip.3",
"@root/acme": "^3.0.5",
"@root/csr": "^0.8.1",
"@root/keypairs": "^0.9.0",
"@root/mkdirp": "^1.0.0",
"@root/request": "^1.3.10",
"acme-dns-01-digitalocean": "^3.0.1",
"acme-http-01-standalone": "^3.0.0",
"cert-info": "^1.5.1",
"greenlock-manager-fs": "^0.1.0",
"greenlock-store-fs": "^3.0.2",
"safe-replace": "^1.1.0"
},

View File

@ -3,7 +3,7 @@
var U = module.exports;
var promisify = require('util').promisify;
var resolveSoa = promisify(require('dns').resolveSoa);
//var resolveSoa = promisify(require('dns').resolveSoa);
var resolveMx = promisify(require('dns').resolveMx);
var punycode = require('punycode');
var Keypairs = require('@root/keypairs');
@ -165,18 +165,28 @@ U._genKeypair = function(keyType) {
// TODO use ACME._importKeypair ??
U._importKeypair = function(keypair) {
// this should import all formats equally well:
// 'object' (JWK), 'string' (private key pem), kp.privateKeyPem, kp.privateKeyJwk
if (keypair.private || keypair.d) {
return U._jwkToSet(keypair.private || keypair);
}
if (keypair.privateKeyJwk) {
return U._jwkToSet(keypair.privateKeyJwk);
}
if (!keypair.privateKeyPem) {
if ('string' !== typeof keypair && !keypair.privateKeyPem) {
// TODO put in errors
throw new Error('missing private key');
}
return Keypairs.import({ pem: keypair.privateKeyPem }).then(function(priv) {
return U._jwkToSet(priv);
});
return Keypairs.import({ pem: keypair.privateKeyPem || keypair }).then(
function(priv) {
if (!priv.d) {
throw new Error('missing private key');
}
return U._jwkToSet(priv);
}
);
};
U._jwkToSet = function(jwk) {
@ -263,7 +273,9 @@ U._getOrCreateKeypair = function(db, subject, query, keyType, mustExist) {
};
U._getKeypair = function(db, subject, query) {
return U._getOrCreateKeypair(db, subject, query, '', true).then(function (result) {
return result.keypair;
});
return U._getOrCreateKeypair(db, subject, query, '', true).then(function(
result
) {
return result.keypair;
});
};