Identity management on Hyperledger Fabric - hyperledger-fabric

I am using Hyperledger fabric 1.4.1 and the latest versions of fabric-contract-api for the smart contracts, fabric-client for the low level APIs to manage channel creation and chaincodes, and fabric-network for interaction with the peers. I have referred to this sample for setting up my network.
I have written a basic chaincode package in nodejs and setup a simple 1 org, 1 peer, 1 orderer network. The first step is to connect to the peer node and use the fabric-ca-client to create the admin identity. As per examples the enrollment id and secret used is admin, adminpw. This is dependent on the configuration used here.
The code I use to create and join a channel, followed by installing and instantiating a chaincode is
const CAClient = require('fabric-ca-client');
const client = require('fabric-client');
const User = client.User;
const fs = require('fs');
const path = require('path');
const basePath = path.resolve(__dirname, '../certs');
const readCryptoFile = filename => fs.readFileSync(path.resolve(basePath, filename)).toString();
const ccPath = path.resolve(__dirname, '../chaincode/src/ax-chaincode');
const url = require('url');
const http = require('http');
let myClient = new client();
const ordererConfig = {
hostname: 'orderer0',
url: 'grpc://localhost:7050',
pem: readCryptoFile('ordererOrg.pem')
};
const orderer = myClient.newOrderer(ordererConfig.url, {
pem: ordererConfig.pem,
'ssl-target-name-override': ordererConfig.hostname
});
let peerConfig = {
hostname: 'ax-peer',
url: 'grpc://localhost:7051', // change to grpcs://ax-peer:7051 in some condition (no idea?)
eventHubUrl: 'grpc://localhost:7053',
pem: readCryptoFile('axOrg.pem')
};
const defaultPeer = myClient.newPeer(peerConfig.url, {
pem: peerConfig.pem,
'ssl-target-name-override': peerConfig.hostname
});
// console.log(defaultPeer);
myClient.setStateStore(await client.newDefaultKeyValueStore({
path: './ax-peer'
}))
let url = 'http://localhost:7054'
const ca = new CAClient(url, {
verify: false
});
let enrollmentID = 'admin';
let enrollmentSecret = 'adminpw';
const enrollment = await ca.enroll({
enrollmentID: 'admin',
enrollmentSecret: 'adminpw'
});
user = new User(enrollmentID, myClient);
// console.log(enrollment);
await user.setEnrollment(enrollment.key, enrollment.certificate, 'AxOrgMSP');
The above will check if admin user is available in the state store. Some queries regarding the above process
The admin user generated here can be used to interact with any and all peers of the same org, assuming only one CA is used?
What is a practical use of this identity, since for the rest of the functions, the admin identity generated by cryptogen for each peer is used (code below)
While enrolling the admin, no attrs are passed along in ca.enroll(), so naturally when querying the identity roles field returns null. The ca-server link shared clearly assigns roles of client, user, peer, validator, auditor to it. Shouldn't that reflect here since it uses admin and adminpw for enrolling id and secret?
Continuing the code
// crypto material got from cryptogen and shifted to new folder
let adminUser = await myClient.createUser({
username: `Admin#ax-peer`,
mspid: 'AxOrgMSP',
cryptoContent: {
privateKeyPEM: readCryptoFile('Admin#ax-org-key.pem'),
signedCertPEM: readCryptoFile('Admin#ax-org-cert.pem')
}
});
let txId = myClient.newTransactionID();
let envelope_bytes = fs.readFileSync('./channel.tx');
let channelConfig = myClient.extractChannelConfig(envelope_bytes);
let signature = myClient.signChannelConfig(channelConfig);
const request = {
name: 'default',
orderer: orderer,
config: channelConfig,
signatures: [signature],
txId: txId
};
const response = await myClient.createChannel(request); // should be 200
// rest of code joins channel, installs and instantiates chaincode
// docker logs show init function being called in new cc container
The stateStore has two files in it (as expected), called admin and Admin#ax-peer. These look like
"name": "Admin#ax-peer",
"mspid": "AxOrgMSP",
"roles": null,
"affiliation": "",
"enrollmentSecret": "",
enrollment: {
"signingIdentity": "554a5f5cfc5a59231a04b7b051bcbcb4f79c4226ff336a4aa48b551de4a8428f",
"certificate": "-----BEGIN CERTIFICATE----- xyz -----END CERTIFICATE-----"
}
When this user is used from the state store by await myClient.getUserContext('admin', true);, how does the client sign the transactions? I am unable to locate the private key/ cert for any user created using the fabric-client SDK.
Now If I use fabric-network API, a FileSystemWallet() function is implemented that stores the private and public cert for each user made by it.
const enrollment = await ca.enroll({ enrollmentID: `admin`, enrollmentSecret: `adminpw` });
const identity = X509WalletMixin.createIdentity('AxOrgMSP', enrollment.certificate, enrollment.key.toBytes());
wallet.import('admin', identity);
This function serves the same purpose as the ca.enroll() but stores the private cert in a visible manner. If I want to use the users created by fabric-client in the fabric-network SDK, I would need to shift the certificates, how do I achieve this?
TLDR - Register user using fabric-client and then use Gateway() class from fabric-network to submit transactions with the same user.
Any advice, guidance is much appreciated. Thanks!

There are too many questions in this stack overflow, so all I will give is some insight into what you have posted.
The admin identity you enroll from the ca server is called admin because it is a registrar of the fabric ca server and thus can register more identities in the ca server. It is not an admin identity for anything else such as the fabric network.
You have used both the lower level api and the fabric-network mechanism for identity management. In the lower level api all that got persisted to the file system was the state store, the public cert and private key are stored in memory purely because of the way you have called everything it just happens to setup defaults in that manner, unfortunately other approaches using the low level api may require you to explicitly set the cryptosuite with a cryptoKeyStore. In the wallet implementation you probably used the file system wallet, so everything got persisted to the file system.
My recommendation is to start everything with a fabric-network gateway if you can. Then if you need to drop to the lower level api's because the fabric-network doesn't do what you need then you can call
gateway.getClient()
or
network.getChannel()
to get a client/channel that has been pre-configured with the gateway configuration and identity.
To get access to a Certificate Authority you can use
gateway.getClient().getCertificateAuthority()
All of this allows you to use the wallet implementation for identity management (and the wallet implementation provides different persistence mechanisms such as in memory, file system, couchdb or you can write your own)

Related

Hyperledger Fabric Smart Contract with private data works in vscode but not in external app. How can I write data to multiple implicit collections?

Summary: An external app can submit a transaction that writes to a single implicit private collection but fails when writing to 2 implicit collections. Everything works OK when using the vscode blockchain extension instead of the external app.
Details:
I am using the vscode blockchain extension (v2.0.8) When used, it installs microfab version 0.0.11.
I am using a 2-org network created in the vscode extension using the 2-org template.
I have a Smart Contract that writes data to 2 implicit private collections (for Org1 and Org2).
Here is the relevant portion of the smart contract (typescript):
#Transaction()
public async createMyPrivateAsset(ctx: Context, myPrivateAssetId: string): Promise<void> {
const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId);
if (exists) {
throw new Error(`The asset my private asset ${myPrivateAssetId} already exists`);
}
const privateAsset: MyPrivateAsset = new MyPrivateAsset();
const transientData: Map<string, Uint8Array> = ctx.stub.getTransient();
if (transientData.size === 0 || !transientData.has('privateValue')) {
throw new Error('The privateValue key was not specified in transient data. Please try again.');
}
privateAsset.privateValue = transientData.get('privateValue').toString();
const collectionName: string = await getCollectionName(ctx, ctx.clientIdentity.getMSPID());
await ctx.stub.putPrivateData(collectionName, myPrivateAssetId, Buffer.from(JSON.stringify(privateAsset)));
}
#Transaction()
public async createMyPrivateAssetMultiple(ctx: Context, myPrivateAssetId: string): Promise<void> {
const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId);
if (exists) {
throw new Error(`The asset my private asset ${myPrivateAssetId} already exists`);
}
const privateAsset: MyPrivateAsset = new MyPrivateAsset();
const transientData: Map<string, Uint8Array> = ctx.stub.getTransient();
if (transientData.size === 0 || !transientData.has('privateValue')) {
throw new Error('The privateValue key was not specified in transient data. Please try again.');
}
privateAsset.privateValue = transientData.get('privateValue').toString();
for (const mspid of ['Org1MSP', 'Org2MSP']) {
var collectionName: string = await getCollectionName(ctx, mspid);
await ctx.stub.putPrivateData(collectionName, myPrivateAssetId, Buffer.from(JSON.stringify(privateAsset)));
}
}
#Transaction(false)
#Returns('MyPrivateAsset')
public async readMyPrivateAsset(ctx: Context, myPrivateAssetId: string): Promise<string> {
const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId);
if (!exists) {
throw new Error(`The asset my private asset ${myPrivateAssetId} does not exist`);
}
let privateDataString: string;
const collectionName: string = await getCollectionName(ctx, ctx.clientIdentity.getMSPID());
const privateData: Uint8Array = await ctx.stub.getPrivateData(collectionName, myPrivateAssetId);
privateDataString = JSON.parse(privateData.toString());
return privateDataString;
}
createMyPrivateAsset writes to a single implicit collection: everything OK.
createMyPrivateAssetMultiple writes to 2 implicit collections: fails in external app.
Both transactions work perfectly when I use the vscode Transaction View to submit transactions.
For createMyPrivateAssetMultiple, I submit using the Org1 gateway and then call readMyPrivateAsset using the Org1 gateway and also using the Org2 gateway and the private data is returned correctly.
Now, when I use an external app, the transaction createMyPrivateAsset works but createMyPrivateAssetMultiple does not.
Here is the relevant portion of the app (typescript):
// connection
const gateway: Gateway = new Gateway();
const connectionProfilePath: string = path.resolve(__dirname, '..', connectionFile);
const connectionProfile = JSON.parse(fs.readFileSync(connectionProfilePath, 'utf8'));
const connectionOptions: GatewayOptions = { wallet, identity: identity, discovery: { enabled: true, asLocalhost: true } };
await gateway.connect(connectionProfile, connectionOptions);
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('private-contract');
Here is the transaction submission for createMyPrivateAsset
let transientData = {
'privateValue': Buffer.from(`Private value for asset ${assetId}`)
};
const trans:Transaction = contract.createTransaction('createMyPrivateAsset');
const buffer: Buffer = await trans.setTransient(transientData).submit(assetId);
This works fine in the app.
Here is the code for createMyPrivateAssetMultiple
let transientData = {
'privateValue': Buffer.from(`Private value for asset ${assetId}`)
};
const trans:Transaction = contract.createTransaction('createMyPrivateAssetMultiple');
const buffer: Buffer = await trans.setTransient(transientData).submit(assetId);
For this transaction, the app throws this (using Org1 gateway):
2022-06-07T13:21:50.727Z - warn: [TransactionEventHandler]: strategyFail: commit failure for transaction "4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f": TransactionError: Commit of transaction 4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f failed on peer org1peer-api.127-0-0-1.nip.io:8084 with status ENDORSEMENT_POLICY_FAILURE
The microfab docker container log includes this:
> WARN 0ff Failed fetching private data from remote peers for dig2src:[map[{b16526e4cd2ac3f431103cda23a6f64adc12acab0550eff18c1f25f1cc0d8bc1 private-contract _implicit_org_Org2MSP 6 0}:[]]], err: Empty membership channel=mychannel
...
[ org2peer] 2022-06-07 13:22:50.794 UTC [gossip.privdata] RetrievePvtdata -> WARN 220 Could not fetch all 1 eligible collection private write sets for block [20] (0 from local cache, 0 from transient store, 0 from other peers). Will commit block with missing private write sets:[txID: 4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f, seq: 0, namespace: private-contract, collection: _implicit_org_Org2MSP, hash: 3e0f263d2edcfaf29df346504a40fdbadce0807938f204fe3e6bf753b751d9a3
Also, package.json includes this:
"dependencies": {
"fabric-network": "~2.1.0"
},
Can anyone shed light on this problem?
You should definitely be using fabric-network#2.2.x, not 2.1.x.
I suspect what is happening is the VS Code client is not using service discovery and sending proposals for endorsement to all network peers, whereas the standalone client application is using service discovery by default and only sending proposals to orgs required by the chaincode endorsement policy. To get it to consider the collection endorsement policy you would need to add a chaincode interest to the Contract object before submitting transactions:
https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.Contract.html#addDiscoveryInterest
There is a working example of this in the Fabric samples:
https://github.com/hyperledger/fabric-samples/blob/8ca50df4ffec311e59451c2a7ebe210d9e6f0004/asset-transfer-private-data/application-javascript/app.js#L166-L178
Alternatively you could either:
Disable service discovery in the Gateway connection options.
Explicitly set the endorsing orgs for a given transaction invocation:
https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.Transaction.html#setEndorsingOrganizations
In general it's much better to be using service discovery so I would not recommend option 1.
The best approach would actually be to use Fabric v2.4+ and the Fabric Gateway client API:
https://hyperledger.github.io/fabric-gateway/
With this API the client (generally) does not need to worry about the organizations required for endorsement when using private data collections or state-/key-based endorsement policies. Things just work automagically.

Hyperledger Fabric get User Private Key from Node.js SDK 2.2 version

I am building a Node.js application that uses the Hyperledger Fabric SDK 2.2 version that interacts with the blockchain network.
What I want to achieve, is to retrieve the Private Key of the user making the request from the Node.js application after retrieved the identity of the wallet.
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
logger.info(`API/` + requestType + `: Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.get(in_username);
if (!userExists) {
logger.info('API/` + requestType + `: An identity for the user: '+in_username+' does not exist in the wallet');
//return 'An identity for the user: "'+in_username+'" does not exist in the wallet';
throw new Error('An identity for the user: "'+in_username+'" does not exist in the wallet');
}
if (userExists && userExists.type === 'X.509') {
console.log("userPrivateKey 1 : " + userExists.type);
const privateKey = (userExists as X509Identity).credentials.privateKey;
console.log("userPrivateKey 1 : " + privateKey);
}
So, I have seen from the documentation the above example of retrieving the private key. I retrieve successfully the identity and get the type of the certificate which is truly X509.
But after that, I am unable to cast or convert the identity to X509 Identity in order to retrieve the private key.
At first I am not able to proceed as this error comes up in the row:
const privateKey = (userExists as X509Identity).credentials.privateKey;
Error:
Type assertion expressions can only be used in TypeScript files.
I am not expert in Node.js and I have been informed that this might not be possible to "cast". But I am confused since I have seen that in the documentation of the Hyperledger Fabric Node SDK.
Anyone knows any solution on that, or even a hint on how to continue?
Thank you
I was going too complex!
Since interface of X509 is extended from the Identity interface then the following code is working:
const privateKey = userExists.credentials.privateKey;
Easier than it seems and also tricky. Thanks for your time!

Hyperledger Fabric 2.0, Can't Access Fabtokens of Users Using Node.js SDK

I'm trying to issue some Fabtokens to users and then use them in various scenarios such as transferring, redeeming, etc. I follow the Node SDK documentation here: https://fabric-sdk-node.github.io/master/tutorial-fabtoken.html
This is how they do the Fabtoken operations:
// create a TokenClient instance from client
const tokenclient = client.newTokenClient(mychannel);
// create a transaction ID for "issuer"
const txId = client.newTransactionID();
// create two parameters for issue, one for user1 and one for user2
const param1 = {
owner: user1.getIdentity().serialize(),
type: 'USD',
quantity: '500',
};
const param2 = {
owner: user2.getIdentity().serialize(),
type: 'EURO',
quantity: '300',
};
// create the token request for issue
const issueRequest = {
params: [param1, param2],
txId: txId,
};
// issuer calls issue method to issue tokens to user1 and user2
const result = await tokenClient.issue(issueRequest);
And then use a different tokenClient to list the tokens of user 1:
const user1Tokenclient = client1.newTokenClient(mychannel);
// user1 lists tokens
const mytokens = await user1TokenClient.list();
// iterate the tokens to get token id, type, and quantity for each token
for (const token of tokens) {
// get token.id, token.type, and token.quantity
// token.id will be used for transfer and redeem
}
It's mentioned on the Node SDK's Client class page here: https://fabric-sdk-node.github.io/master/Client.html that switching userContexts with the same client instance is an anti-pattern and not recommended since client instances are stateful.
As they suggest, I create my client instances with different user contexts. This is how I create my clients, set their user context and create my tokenClient instances:
const adminClient = new Fabric_Client();
const admin = await adminClient.createUser(user_opts);
adminClient.setUserContext(admin, true);
let adminConfig = {
admin: admin,
adminClient: adminClient,
adminTokenClient: adminClient.newTokenClient(channel)
}
const server = await serverClient.createUser(server_opts);
serverClient.setUserContext(server, true);
let serverConfig = {
server: server,
serverClient: serverClient,
serverTokenClient: serverClient.newTokenClient(channel)
}
Later on, I'm using these config objects to issue some tokens to different users. How I issue tokens to my server account from my issuer (admin) account:
const txId = adminConfig.adminClient.newTransactionID();
let issueQuery = {
tokenClient: adminConfig.adminTokenClient,
txId: txId,
channel: channel,
params: []
}
for(let i=0; i < 3; ++i) {
let param = {
owner: serverConfig.server.getIdentity().serialize(),
type: 'test',
quantity: '1'
}
issueQuery.params.push(param);
}
let issueTx = await waitForIssue(issueQuery);
This successfully issue three tokens to the server as expected. The problem is that when I try to access to the tokens of my server like the example they provide using a similar code:
let server_tokens = await serverConfig.serverTokenClient.list();
for (let server_token of server_tokens) {
console.log(server_token.id);
}
Result is just empty and I don't get any error messages. However, when I check the transaction using queryTransaction(txId) for the token issue transaction I generate, I can see that owner of the issued tokens in that transaction is the server and that's how I can be sure that I can successfully issue the tokens to the server. Is there any other way to check the tokens of my server? Or shouldn't I use a different client and user context per each user as they suggest? Because, previously I was able to see the tokens of the server when I used a single client and single user context to issue and list tokens. But this approach caused me problems when I was trying to transfer my tokens asynchronously.
As far as I know FabTokens are removed from the Master branch of Fabric 2.0 now as described in these links:
https://gerrit.hyperledger.org/r/c/fabric/+/32979
https://lists.hyperledger.org/g/fabric/topic/fabtoken/34150195?p=,,,20,0,0,0::recentpostdate%2Fsticky,,,20,2,0,34150195
I would expect the tutorial and the information in the Fabric docs to be removed in due course.

How to manage user authentication for hyperledger using msp and fabric-ca-client?

I am developing an application over fabric 1.3 . I have built a network on multi-node setup, connected peers, instantiate chaincode and have my network up and ready for invocation and queries.
Now, I am thinking to make a log-in portal through which a user can register/enroll and perform invoke/queries. All my peers and orderer are on cloud, and am planning to provide this log-in feature using the Node SDK exposed on a cloud instance.
I went through the official doc:
https://hyperledger-fabric-ca.readthedocs.io/en/latest/users-guide.html#registering-a-new-identity
I can see that we need fabric-ca component to register users and enroll them for queries. Upon enrollment, we get a cert files under ~/.hfc-key-store.
Now I want to understand how should I go ahead with my flow.
User signs up on network:
fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1'}, admin_user)
User log in with his secret:
fabric_ca_client.enroll({enrollmentID: 'user1', enrollmentSecret: secret});
}).then((enrollment) => {
console.log('Successfully enrolled member user "user1" ');
return fabric_client.createUser(
{username: 'user1',
mspid: 'Org1MSP',
cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate }
});
}).then((user) => {
member_user = user;
return fabric_client.setUserContext(member_user);
Invoke/Query as user1:
var store_path = path.join(os.homedir(), '.hfc-key-store');
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
// assign the store to the fabric client
fabric_client.setStateStore(state_store);
var crypto_suite = Fabric_Client.newCryptoSuite();
// use the same location for the state store (where the users' certificate are kept)
// and the crypto store (where the users' keys are kept)
var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
crypto_suite.setCryptoKeyStore(crypto_store);
fabric_client.setCryptoSuite(crypto_suite);
// get the enrolled user from persistence, this user will sign all requests
return fabric_client.getUserContext('user1', true);
}).then((user_from_store) => {
if (user_from_store && user_from_store.isEnrolled()) {
console.log('Successfully loaded user1 from persistence');
member_user = user_from_store;
} else {
throw new Error('Failed to get user1.... run registerUser.js');
}..
Now, what shall I do when a user logs out? delete the ~/.hfc-key-store certs? Since these certs are going to be stored on server side where Node script is running, so it doesn't make sense.
Also, is my flow correct or if there is any better way to accomplice my objective?
I had a similar login implementation that I had to do, As you said I did create certificates for each registered user as well as stored the basic user information in MongoDB.
The flow that I went with is that once the user is registered the user certification is created as well as his login credentials such as username and password are stored in MongoDB.
When the user tries to login back, I would check the MongoDB as well as the certification to see if the user has already registered, once logged in the user would then be in possession of an auth token which he then can use to interact with the fabric-client.
When an identity (certs and keys) is issued by CA then it should be persisted for that particular user for future interactions (either by saving in client wallet or other methods) so if you delete it then there will be re-enroll process on each log in and it will slow it down too.
To resolve it -
1. Create separate logic like JWT token for log in and session management.
2. Save the keys and certs on server directory (not the best way but will work for now later)
Let me know if it satisfies your query.
Maybe a little late but maybe it can help someone. My approach is to do what you do in the login, in a register method where I return the certificate and the private key, then on login the user needs to provide both the certificate and the private keys generated (and also the certificate and the private key for the TLS connection), and with this information I recreate de Identity and store it in a MemoryWallet, and with this MemoryWallet now, I can create the gateway and connect to the blockchain.
It would be something like this
const identity = X509WalletMixin.createIdentity(mspId, certificate, privateKey);
const identityTLS = X509WalletMixin.createIdentity(mspId, certificateTLS, privateKeyTLS);
const wallet = new InMemoryWallet();
await wallet.import(userId, identity);
await wallet.import(userId + '-tls', identityTLS);
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: userId, discovery: { enabled: true, asLocalhost: false } });
const client = gateway.getClient();
const userTlsCert = await wallet.export(userId + '-tls') as any;
client.setTlsClientCertAndKey(userTlsCert.certificate, userTlsCert.privateKey);
Hope it helps

How to share user certificates to clients

Which is the most appropriate way to share client certificates to end users of a Hyperledger fabric network?
I have already set up a java sdk client to Register and enroll users using admin Credentials. At the end of the scenario i have a Username and a Password for each user. What i cannot find is where client certificates are stored at the local MSP and how i can share them to the actual end users.
Any recommendation or example of a proposed solution will be appreciated.
while enrolling user,
you will get enrollment object
Irrespective of SDK (NODE, JAVA, GO)
let enrollment = await caClient.enroll(request)
const key = enrollment.key.toBytes();
const cert = enrollment.certificate;
response.key = key;
response.cert = cert;
response.secret = secret;
return response;
LOOKS like below result
"data": { "message": "nbdClient has been enrolled Successfully to
Org: nbd", "key": "-----BEGIN PRIVATE
KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgH7ttkV4VIDA1TlSx\r\n/bmsy1Ad6zgLGhjdcFtElexqAtShRANCAASHKIOk+nBTIqfn5taiqMWlRnfHKdth\r\nkZKyq9Up4wl+PsBEQByyKfaDV904APCQ7zDvmPtwxsdNGxA76V4EpAqO\r\n-----END
PRIVATE KEY-----\r\n", "cert": "-----BEGIN
CERTIFICATE-----\nMIICjTCCAjOgAwIBAgIUA85ydnzJXRoRxeW5v2lrNk5pe+swCgYIKoZIzj0EAwIw\nWTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xDDAKBgNVBAoTA25iZDEPMA0GA1UEAxMGY2EubmJkMB4XDTE5\nMDgxNTE0MjQwMFoXDTIwMDgxNDE0MjkwMFowLzEZMAsGA1UECxMEdXNlcjAKBgNV\nBAsTA25iZDESMBAGA1UEAxMJbmJkQ2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEhyiDpPpwUyKn5+bWoqjFpUZ3xynbYZGSsqvVKeMJfj7AREAcsin2g1fd\nOADwkO8w75j7cMbHTRsQO+leBKQKjqOCAQEwgf4wDgYDVR0PAQH/BAQDAgOoMB0G\nA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1Ud\nDgQWBBRMV5nFrtT3IpichyfayTaACbqpQDArBgNVHSMEJDAigCBLcH2ot2qhX7wR\nCP6IeDXWkgXitZ3TukhQLBZFzboaWzBzBggqAwQFBgcIAQRneyJhdHRycyI6eyJo\nZi5BZmZpbGlhdGlvbiI6Im5iZCIsImhmLkVucm9sbG1lbnRJRCI6Im5iZENsaWVu\ndCIsImhmLlR5cGUiOiJ1c2VyIiwibmJkVXNlcjEiOiJuYmRVc2VyMSJ9fTAKBggq\nhkjOPQQDAgNIADBFAiEA9/Rqd9/WtWLkR+XE1MdS4gX/JdYTqU58E8KMaShwFmkC\nIDkA2OsC0jRswweTHmzGk5z5gKcwhOrZbJTZagqpv2m4\n-----END
CERTIFICATE-----\n", "secret": "HqknKlBGzibb" }

Resources