@microsoft/useragent-sdk
Version:
SDK for building decentralized identity wallets and enterprise agents.
355 lines (319 loc) • 13.1 kB
text/typescript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import IdentifierDocument from '../src/IdentifierDocument';
import Identifier from '../src/Identifier';
import UserAgentOptions from '../src/UserAgentOptions';
import TestResolver from './resolvers/TestResolver';
import UserAgentError from '../src/UserAgentError';
import CryptoOptions from '../src/CryptoOptions';
import TestRegistrar from './registrars/TestRegistrar';
import { KeyType } from '../src/crypto/keys/KeyTypeFactory';
import { KeyUse } from '../src/crypto/keys/KeyUseFactory';
import KeyStoreInMemory from '../src/crypto/keyStore/KeyStoreInMemory';
import JwsToken from '../src/crypto/protocols/jose/jws/JwsToken';
import OctKey from '../src/crypto/keys/Oct/OctKey';
import CryptoFactory from '../src/crypto/plugin/CryptoFactory';
import SubtleCryptoNode from '../src/crypto/plugin/SubtleCryptoNode';
import KeyContainer from '../src/crypto/keys/KeyContainer';
describe('Identifier', () => {
let testResolver: TestResolver;
let options: UserAgentOptions;
beforeAll(() => {
testResolver = new TestResolver();
// Configure the agent options for the tests
options = new UserAgentOptions();
options.resolver = testResolver;
options.timeoutInSeconds = 30;
});
it('should construct a storage identfier for the key', () => {
let identifier = Identifier.keyStorageIdentifier('did:ion:identifier', 'peer', KeyUse.Encryption, KeyType.EC);
expect(identifier).toEqual('did:ion:identifier-peer-enc-EC');
identifier = Identifier.keyStorageIdentifier('did:ion:identifier', 'peer', KeyUse.Encryption, KeyType.RSA);
expect(identifier).toEqual('did:ion:identifier-peer-enc-RSA');
identifier = Identifier.keyStorageIdentifier('did:ion:identifier', 'peer', KeyUse.Signature, KeyType.EC);
expect(identifier).toEqual('did:ion:identifier-peer-sig-EC');
identifier = Identifier.keyStorageIdentifier('did:ion:identifier', 'peer', KeyUse.Signature, KeyType.RSA);
expect(identifier).toEqual('did:ion:identifier-peer-sig-RSA');
});
it('should construct new instance when provided an identifier string', () => {
const identifier = new Identifier('did:ion:identifier', options);
expect(identifier).toBeDefined();
expect(identifier.id).toEqual('did:ion:identifier');
});
it('should construct new instance when provided an identifier document', () => {
const identifierDocument = new IdentifierDocument(
{ id: 'did:ion:identifier', created: '2019-01-25T01:08:44.732Z' }
);
const identifier = new Identifier(identifierDocument, options);
expect(identifier).toBeDefined();
expect(identifier.id).toEqual('did:ion:identifier');
});
it('should return document from local', () => {
const identifierDocument = new IdentifierDocument(
{ id: 'did:ion:identifier', created: '2019-01-25T01:08:44.732Z' }
);
const identifier = new Identifier(identifierDocument, options);
const resolver = spyOn<any>(testResolver, 'resolve').and.callThrough();
const localDocument = identifier.getDocument();
expect(localDocument).toBeDefined();
expect(resolver).not.toHaveBeenCalled();
});
it('should throw when no resolver specified and document not cached', async done => {
const options: UserAgentOptions = <UserAgentOptions> {
timeoutInSeconds: 30
};
const identifier = new Identifier('did:ion:identifier', options);
await identifier
.getDocument()
.catch(error => {
expect(error instanceof UserAgentError).toBeTruthy();
expect(error.message).toEqual('Resolver not specified in user agent options.');
})
.finally(done);
});
it('should resolve identifier and return document', () => {
const identifier = new Identifier('did:ion:identifier', options);
const identifierDocument = new IdentifierDocument(
{ id: 'did:ion:identifier', created: '2019-01-25T01:08:44.732Z' }
);
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
const resolver = spyOn(testResolver, 'resolve').and.callThrough();
const result = identifier.getDocument();
expect(result).toBeDefined();
expect(resolver).toHaveBeenCalled();
});
it('should call getDocument() when local document undefined', async () => {
const identifier = new Identifier('did:ion:identifier', options);
const identifierDocument = new IdentifierDocument(
{
id: 'did:ion:identifier',
created: '2019-01-25T01:08:44.732Z',
publicKeys: [
{
id: '#master',
type: 'RsaVerificationKey2018',
publicKeyJwk: {
kty: 'RSA',
kid: '#master',
keyOps: [
'sign',
'verify'
],
n: 'vdpHn7kNq42UMC1W8bwxgE7K...',
e: 'AQAB'
}
}
]
}
);
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
const resolver = spyOn(testResolver, 'resolve').and.callThrough();
const result = await identifier.getPublicKey('#master');
expect(result).toBeDefined();
expect(resolver).toHaveBeenCalled();
});
it('should throw when document has no keys', async done => {
const identifier = new Identifier('did:ion:identifier', options);
const identifierDocument = new IdentifierDocument(
{
id: 'did:ion:identifier',
created: '2019-01-25T01:08:44.732Z'
}
);
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
await identifier
.getPublicKey('#master')
.catch(error => {
expect(error instanceof UserAgentError).toBeTruthy();
expect(error.message).toEqual('Document does not contain any public keys');
})
.finally(done);
});
it('should throw when document does not contain specified key', async done => {
const identifier = new Identifier('did:ion:identifier', options);
const identifierDocument = new IdentifierDocument(
{
id: 'did:ion:identifier',
created: '2019-01-25T01:08:44.732Z',
publicKeys: [
{
id: '#master',
type: 'RsaVerificationKey2018',
publicKeyJwk: {
kty: 'RSA',
kid: '#master',
keyOps: [
'sign',
'verify'
],
n: 'vdpHn7kNq42UMC1W8bwxgE7K...',
e: 'AQAB'
}
}
]
}
);
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
await identifier
.getPublicKey('#notInDocument')
.catch(error => {
expect(error instanceof UserAgentError).toBeTruthy();
expect(error.message).toEqual(`No matching public key found for #notInDocument`);
})
.finally(done);
});
it('should return public key for specified key identifier', async () => {
const identifier = new Identifier('did:ion:identifier', options);
const identifierDocument = new IdentifierDocument(
{
id: 'did:ion:identifier',
created: '2019-01-25T01:08:44.732Z',
publicKeys: [
{
id: '#master',
type: 'RsaVerificationKey2018',
publicKeyJwk: {
kty: 'RSA',
kid: '#master',
keyOps: [
'sign',
'verify'
],
n: 'vdpHn7kNq42UMC1W8bwxgE7K...',
e: 'AQAB'
}
}
]
}
);
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
const publicKey = await identifier.getPublicKey('#master');
expect(publicKey).toBeDefined();
expect(publicKey.id).toEqual('#master');
});
it('should return first public key when no key identifier specified', async () => {
const identifier = new Identifier('did:ion:identifier', options);
const identifierDocument = new IdentifierDocument(
{
id: 'did:ion:identifier',
created: '2019-01-25T01:08:44.732Z',
publicKeys: [
{
id: '#first',
type: 'RsaVerificationKey2018',
publicKeyJwk: {
kty: 'RSA',
kid: '#master',
keyOps: [
'sign',
'verify'
],
n: 'vdpHn7kNq42UMC1W8bwxgE7K...',
e: 'AQAB'
}
},
{
id: '#second',
type: 'RsaVerificationKey2018',
publicKeyJwk: {
kty: 'RSA',
kid: '#master',
keyOps: [
'sign',
'verify'
],
n: 'vdpHn7kNq42UMC1W8bwxgE7K...',
e: 'AQAB'
}
}
]
}
);
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
const publicKey = await identifier.getPublicKey();
expect(publicKey).toBeDefined();
expect(publicKey.id).toEqual('#first');
});
describe('sign', () => {
let options: UserAgentOptions;
beforeEach(() => {
options = new UserAgentOptions();
options.resolver = testResolver;
options.timeoutInSeconds = 30;
options.didPrefix = 'did:ion';
options.registrar = new TestRegistrar();
});
it('should sign a payload that is a string', async () => {
options.cryptoOptions = new CryptoOptions(new CryptoFactory(new KeyStoreInMemory(), SubtleCryptoNode.getSubtleCrypto()));
options.cryptoOptions.signingAlgorithm = 'ES256K';
const seed = new OctKey('ABDE');
await options.keyStore.save('masterSeed', new KeyContainer(seed));
const identifier = await Identifier.create(options);
const keystore = await options.keyStore.list();
console.log(keystore);
const signedPayload = await identifier.sign('examplePayload', 'did:ion-did:ion-ES256K-sig');
expect(signedPayload).toBeDefined();
expect(JwsToken.deserialize(signedPayload).getPayload()).toEqual('examplePayload');
});
it('should sign a payload that is an object', async () => {
options.cryptoOptions = new CryptoOptions(new CryptoFactory(new KeyStoreInMemory(), SubtleCryptoNode.getSubtleCrypto()));
options.cryptoOptions.signingAlgorithm = 'ES256K';
options.keyStore = new KeyStoreInMemory();
const seed = new OctKey('ABDE');
await options.keyStore.save('masterSeed', new KeyContainer(seed));
const identifier = await Identifier.create(options);
const signedPayload = await identifier.sign({ payload: 'examplePayload' }, 'did:ion-did:ion-ES256K-sig');
expect(signedPayload).toBeDefined();
expect(JwsToken.deserialize(signedPayload).getPayload()).toEqual(`{"payload":"examplePayload"}`);
});
});
describe('verify', () => {
let identifier: Identifier;
let identifierDocument: IdentifierDocument;
beforeAll(() => {
identifier = new Identifier('did:ion:identifier', options);
identifierDocument = new IdentifierDocument(
{
id: 'did:ion:identifier',
created: '2019-01-25T01:08:44.732Z',
publicKeys: [
{
id: '#master',
type: 'RsaVerificationKey2018',
publicKeyJwk: {
kty: 'RSA',
kid: '#master',
keyOps: [
'sign',
'verify'
],
n: 'vdpHn7kNq42UMC1W8bwxgE7K...',
e: 'AQAB'
}
}
]
}
);
console.log(identifier);
console.log(identifierDocument);
});
/*
const testJws = 'testJWS';
fit('should resolve identifier document and verify jws', async () => {
(<TestResolver> options.resolver).prepareTest(identifier, identifierDocument);
const verifiedPayload = await identifier.verify(testJws);
expect(verifiedPayload).toBeDefined();
expect(verifiedPayload).toBe('verifiedPayload');
});
fit('should verify jws', async () => {
console.log(identifier);
const testIdentifier = new Identifier(identifierDocument, options);
const verifiedPayload = await testIdentifier.verify(testJws);
expect(verifiedPayload).toBeDefined();
expect(verifiedPayload).toBe('verifiedPayload');
});
*/
});
});