generated from garrettmills/template-npm-typescript
	Initial implementation
This commit is contained in:
		
							parent
							
								
									d20132c63e
								
							
						
					
					
						commit
						5131132c4d
					
				
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					# Default ignored files
 | 
				
			||||||
 | 
					/shelf/
 | 
				
			||||||
 | 
					/workspace.xml
 | 
				
			||||||
 | 
					# Datasource local storage ignored files
 | 
				
			||||||
 | 
					/dataSources/
 | 
				
			||||||
 | 
					/dataSources.local.xml
 | 
				
			||||||
 | 
					# Editor-based HTTP Client requests
 | 
				
			||||||
 | 
					/httpRequests/
 | 
				
			||||||
							
								
								
									
										6
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<component name="InspectionProjectProfileManager">
 | 
				
			||||||
 | 
					  <profile version="1.0">
 | 
				
			||||||
 | 
					    <option name="myName" value="Project Default" />
 | 
				
			||||||
 | 
					    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
				
			||||||
 | 
					  </profile>
 | 
				
			||||||
 | 
					</component>
 | 
				
			||||||
							
								
								
									
										6
									
								
								.idea/misc.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/misc.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="ProjectRootManager">
 | 
				
			||||||
 | 
					    <output url="file://$PROJECT_DIR$/out" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="ProjectModuleManager">
 | 
				
			||||||
 | 
					    <modules>
 | 
				
			||||||
 | 
					      <module fileurl="file://$PROJECT_DIR$/.idea/multicrypt.iml" filepath="$PROJECT_DIR$/.idea/multicrypt.iml" />
 | 
				
			||||||
 | 
					    </modules>
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										9
									
								
								.idea/multicrypt.iml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/multicrypt.iml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<module type="JAVA_MODULE" version="4">
 | 
				
			||||||
 | 
					  <component name="NewModuleRootManager" inherit-compiler-output="true">
 | 
				
			||||||
 | 
					    <exclude-output />
 | 
				
			||||||
 | 
					    <content url="file://$MODULE_DIR$" />
 | 
				
			||||||
 | 
					    <orderEntry type="inheritedJdk" />
 | 
				
			||||||
 | 
					    <orderEntry type="sourceFolder" forTests="false" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</module>
 | 
				
			||||||
							
								
								
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="RunConfigurationProducerService">
 | 
				
			||||||
 | 
					    <option name="ignoredProducers">
 | 
				
			||||||
 | 
					      <set>
 | 
				
			||||||
 | 
					        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
 | 
				
			||||||
 | 
					      </set>
 | 
				
			||||||
 | 
					    </option>
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="VcsDirectoryMappings">
 | 
				
			||||||
 | 
					    <mapping directory="" vcs="Git" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
@ -1,3 +1,3 @@
 | 
				
			|||||||
# template-npm-typescript
 | 
					# multicrypt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A template repository for NPM packages built with Typescript and PNPM.
 | 
					Multicrypt is a library for multi-key reversible encryption.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "name": "template-npm-typescript",
 | 
					    "name": "multicrypt",
 | 
				
			||||||
    "version": "0.1.0",
 | 
					    "version": "0.1.0",
 | 
				
			||||||
    "description": "A template for NPM packages built with TypeScript",
 | 
					    "description": "A library for multi-key reversible encryption",
 | 
				
			||||||
    "main": "lib/index.js",
 | 
					    "main": "lib/index.js",
 | 
				
			||||||
    "types": "lib/index.d.ts",
 | 
					    "types": "lib/index.d.ts",
 | 
				
			||||||
    "directories": {
 | 
					    "directories": {
 | 
				
			||||||
@ -21,7 +21,7 @@
 | 
				
			|||||||
    "postversion": "git push && git push --tags",
 | 
					    "postversion": "git push && git push --tags",
 | 
				
			||||||
    "repository": {
 | 
					    "repository": {
 | 
				
			||||||
        "type": "git",
 | 
					        "type": "git",
 | 
				
			||||||
        "url": "https://code.garrettmills.dev/garrettmills/template-npm-typescript"
 | 
					        "url": "https://code.garrettmills.dev/garrettmills/multicrypt"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "author": "Garrett Mills <shout@garrettmills.dev>",
 | 
					    "author": "Garrett Mills <shout@garrettmills.dev>",
 | 
				
			||||||
    "license": "MIT",
 | 
					    "license": "MIT",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										83
									
								
								src/KeyPackage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/KeyPackage.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import {EncodedValue, isEncodedPayload, KeyValue} from './types'
 | 
				
			||||||
 | 
					import {Cryptor, InvalidKeyError} from './crypt/Cryptor'
 | 
				
			||||||
 | 
					import {pkg} from './interface'
 | 
				
			||||||
 | 
					import {Payload} from './Payload'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A collection of keys that are used to decode a base key.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The base key is used to encode various values that can then be accessed
 | 
				
			||||||
 | 
					 * by all of the other keys in the package.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class KeyPackage {
 | 
				
			||||||
 | 
					    /** Create a new key package from an initial sharer's key. */
 | 
				
			||||||
 | 
					    public static async fromInitialSharer(initialSharerKey: KeyValue): Promise<KeyPackage> {
 | 
				
			||||||
 | 
					        // Get the cryptor for the initial sharer
 | 
				
			||||||
 | 
					        const sharerCryptor = pkg.cryptor(initialSharerKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Encode a new global key using the initial sharer's key
 | 
				
			||||||
 | 
					        const envelope = new Payload(Cryptor.getNewKey())
 | 
				
			||||||
 | 
					        const envelopeKey = await sharerCryptor.encode(envelope.encode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Return a new KeyPackage with the created envelope
 | 
				
			||||||
 | 
					        return new KeyPackage([envelopeKey])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Instantiate a key package from its serialized JSON value. */
 | 
				
			||||||
 | 
					    public static fromJSON(value: { envelopes: EncodedValue[] }): KeyPackage {
 | 
				
			||||||
 | 
					        return new KeyPackage(value.envelopes)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        /** The encoded user keys. */
 | 
				
			||||||
 | 
					        private readonly envelopes: EncodedValue[],
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a Cryptor instance for master key given some user's key, if that key is valid.
 | 
				
			||||||
 | 
					     * @param key
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getCryptor(key: KeyValue): Promise<Cryptor> {
 | 
				
			||||||
 | 
					        const envelopeCryptor = pkg.cryptor(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for ( const encodedKey of this.envelopes ) {
 | 
				
			||||||
 | 
					            // Attempt to decode one of the envelope keys using the input key
 | 
				
			||||||
 | 
					            const decodedKey = await envelopeCryptor.decode(encodedKey)
 | 
				
			||||||
 | 
					            if ( isEncodedPayload(decodedKey) ) {
 | 
				
			||||||
 | 
					                // We successfully decoded the envelope, which contains the global key
 | 
				
			||||||
 | 
					                return pkg.cryptor(Payload.from<string>(decodedKey).value)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        throw new InvalidKeyError()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Add a user's key to this package, granting them access to the master key.
 | 
				
			||||||
 | 
					     * @param sharerKey - a currently valid user's key
 | 
				
			||||||
 | 
					     * @param shareeKey - the key to add to the package
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async addCryptor(sharerKey: KeyValue, shareeKey?: KeyValue): Promise<KeyValue> {
 | 
				
			||||||
 | 
					        // Validate the sharer's key and get the cryptor
 | 
				
			||||||
 | 
					        const sharerCryptor = await this.getCryptor(sharerKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create a new cryptor for the sharee
 | 
				
			||||||
 | 
					        const determinedShareeKey = shareeKey || Cryptor.getNewKey()
 | 
				
			||||||
 | 
					        const shareeCryptor = pkg.cryptor(determinedShareeKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Encode the global key using the sharee's key
 | 
				
			||||||
 | 
					        const envelope = new Payload(sharerCryptor.getKey())
 | 
				
			||||||
 | 
					        const envelopeKey = await shareeCryptor.encode(envelope.encode())
 | 
				
			||||||
 | 
					        this.envelopes.push(envelopeKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Return the sharee's key that can be used to decode the global key
 | 
				
			||||||
 | 
					        return determinedShareeKey
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Serialize the key package securely. */
 | 
				
			||||||
 | 
					    toJSON() {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            envelopes: this.envelopes,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/Payload.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Payload.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import {EncodedPayload} from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wrapper class for managing values encoded in a string format.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class Payload<T> {
 | 
				
			||||||
 | 
					    /** Instantiate the payload from a given string. */
 | 
				
			||||||
 | 
					    public static from<T>(v: EncodedPayload<T>): Payload<T> {
 | 
				
			||||||
 | 
					        const encoded = v.slice('multicrypt:'.length)
 | 
				
			||||||
 | 
					        return new Payload<T>(JSON.parse(encoded))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Instantiate the payload from a given value. */
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        public readonly value: T,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Serialize the value. */
 | 
				
			||||||
 | 
					    encode(): EncodedPayload<T> {
 | 
				
			||||||
 | 
					        return `multicrypt:${JSON.stringify(this.value)}`
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										96
									
								
								src/SharedValue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/SharedValue.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					import {EncodedValue, KeyValue} from './types'
 | 
				
			||||||
 | 
					import {KeyPackage} from './KeyPackage'
 | 
				
			||||||
 | 
					import {Payload} from './Payload'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Interface for a JSON-serializable shared value. */
 | 
				
			||||||
 | 
					export interface SerializedSharedValue<T> {
 | 
				
			||||||
 | 
					    value: EncodedValue,
 | 
				
			||||||
 | 
					    envelopes: EncodedValue[],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A value that is shared and accessible by multiple encryption keys.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class SharedValue<T> {
 | 
				
			||||||
 | 
					    /** The package of keys allowed to access the encoded value. */
 | 
				
			||||||
 | 
					    public readonly package: KeyPackage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create a new shared value.
 | 
				
			||||||
 | 
					     * @param initialSharerKey - an initial key, used to give access to the encoded value
 | 
				
			||||||
 | 
					     * @param value - the value to be encoded and shared
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static async create<T>(initialSharerKey: KeyValue, value: T): Promise<SharedValue<T>> {
 | 
				
			||||||
 | 
					        const keyPackage = await KeyPackage.fromInitialSharer(initialSharerKey)
 | 
				
			||||||
 | 
					        const cryptor = await keyPackage.getCryptor(initialSharerKey)
 | 
				
			||||||
 | 
					        const payload = new Payload<T>(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const serialized = {
 | 
				
			||||||
 | 
					            value: await cryptor.encode(payload.encode()),
 | 
				
			||||||
 | 
					            ...(keyPackage.toJSON()),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new SharedValue<T>(serialized)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        /** The serialized data for this shared value. */
 | 
				
			||||||
 | 
					        private serialized: SerializedSharedValue<T>,
 | 
				
			||||||
 | 
					        public readonly validator: (w: unknown) => w is T = (w: unknown): w is T => true,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        this.package = KeyPackage.fromJSON(serialized)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a Payload instance of the decoded value.
 | 
				
			||||||
 | 
					     * @param key - valid key to decode the payload
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async payload(key: KeyValue): Promise<Payload<T>> {
 | 
				
			||||||
 | 
					        const cryptor = await this.package.getCryptor(key)
 | 
				
			||||||
 | 
					        const decodedValue = await cryptor.decode(this.serialized.value)
 | 
				
			||||||
 | 
					        return Payload.from<T>(decodedValue)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the value of this secret.
 | 
				
			||||||
 | 
					     * @param key - valid key to decode the value
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async get(key: KeyValue): Promise<T> {
 | 
				
			||||||
 | 
					        const payload = await this.payload(key)
 | 
				
			||||||
 | 
					        const value = payload.value
 | 
				
			||||||
 | 
					        if ( !this.validator(value) ) {
 | 
				
			||||||
 | 
					            throw new TypeError('Invalid encoded value!')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set a new value for this secret.
 | 
				
			||||||
 | 
					     * @param key - valid key to encode the value
 | 
				
			||||||
 | 
					     * @param value - new value to encode
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async set(key: KeyValue, value: T): Promise<void> {
 | 
				
			||||||
 | 
					        const cryptor = await this.package.getCryptor(key)
 | 
				
			||||||
 | 
					        const payload = new Payload<T>(value)
 | 
				
			||||||
 | 
					        this.serialized.value = await cryptor.encode(payload.encode())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Add a new key to the key package for this shared value. This allows
 | 
				
			||||||
 | 
					     * the `shareeKey` to access this shared value.
 | 
				
			||||||
 | 
					     * @param sharerKey - a currently valid key
 | 
				
			||||||
 | 
					     * @param shareeKey - the new key to add
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async addKey(sharerKey: KeyValue, shareeKey?: KeyValue): Promise<KeyValue> {
 | 
				
			||||||
 | 
					        return this.package.addCryptor(sharerKey, shareeKey)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Serialize the shared value securely. */
 | 
				
			||||||
 | 
					    toJSON(): SerializedSharedValue<T> {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            value: this.serialized.value,
 | 
				
			||||||
 | 
					            ...(this.package.toJSON()),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/crypt/AES256Cryptor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/crypt/AES256Cryptor.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import {Cryptor} from './Cryptor'
 | 
				
			||||||
 | 
					import {Awaitable, DecodedValue, EncodedValue} from '../types'
 | 
				
			||||||
 | 
					import * as crypto from 'crypto'
 | 
				
			||||||
 | 
					import { Buffer } from 'buffer'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * AES 256 implementation of a Cryptor.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class AES256Cryptor extends Cryptor {
 | 
				
			||||||
 | 
					    encode(value: DecodedValue): Awaitable<EncodedValue> {
 | 
				
			||||||
 | 
					        const hash = crypto.createHash('sha256')
 | 
				
			||||||
 | 
					        hash.update(this.key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const vector = crypto.randomBytes(16)
 | 
				
			||||||
 | 
					        const cipher = crypto.createCipheriv('aes-256-ctr', hash.digest(), vector)
 | 
				
			||||||
 | 
					        const input = Buffer.from(value)
 | 
				
			||||||
 | 
					        const cipherText = cipher.update(input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const encoded = Buffer.concat([vector, cipherText, cipher.final()])
 | 
				
			||||||
 | 
					        return encoded.toString('base64')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    decode(payload: EncodedValue): Awaitable<DecodedValue> {
 | 
				
			||||||
 | 
					        const hash = crypto.createHash('sha256')
 | 
				
			||||||
 | 
					        hash.update(this.key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const input = Buffer.from(payload, 'base64')
 | 
				
			||||||
 | 
					        const vector = input.slice(0, 16)
 | 
				
			||||||
 | 
					        const decipher = crypto.createDecipheriv('aes-256-ctr', hash.digest(), vector)
 | 
				
			||||||
 | 
					        const cipherText = input.slice(16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const decoded = Buffer.concat([decipher.update(cipherText), decipher.final()])
 | 
				
			||||||
 | 
					        return decoded.toString()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/crypt/Cryptor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/crypt/Cryptor.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					import {Awaitable, DecodedValue, EncodedValue, Instantiable, isInstantiable, KeyValue} from '../types'
 | 
				
			||||||
 | 
					import * as uuid from 'uuid'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Error thrown when a key fails to decode an encoded value.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class InvalidKeyError extends Error {
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super('The provided key is invalid and cannot be used to decode the payload.')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Base class for implementations that encode and decode values using some key.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export abstract class Cryptor {
 | 
				
			||||||
 | 
					    /** Function used to instantiate the Cryptor. */
 | 
				
			||||||
 | 
					    protected static factoryFunction?: (key: KeyValue) => Cryptor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Generate a new, random encryption key. */
 | 
				
			||||||
 | 
					    public static getNewKey(): string {
 | 
				
			||||||
 | 
					        return uuid.v4().replace(/-/g, '')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a factory that produces an instance of this cryptor.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static getFactory(): (key: KeyValue) => Cryptor {
 | 
				
			||||||
 | 
					        if ( !this.factoryFunction ) {
 | 
				
			||||||
 | 
					            const ctor = this as typeof Cryptor
 | 
				
			||||||
 | 
					            if ( !isInstantiable<Cryptor>(ctor) ) {
 | 
				
			||||||
 | 
					                throw new Error('Cannot create factory for abstract Cryptor.')
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.factoryFunction = key => new (ctor as Instantiable<Cryptor>)(key)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.factoryFunction
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        /** The key used by this cryptor. */
 | 
				
			||||||
 | 
					        protected key: KeyValue,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Using the key, encode the value.
 | 
				
			||||||
 | 
					     * @param value
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public abstract encode(value: DecodedValue): Awaitable<EncodedValue>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Using the key, decode the value.
 | 
				
			||||||
 | 
					     * @param payload
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public abstract decode(payload: EncodedValue): Awaitable<DecodedValue>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Get the key used by this Cryptor. */
 | 
				
			||||||
 | 
					    public getKey(): KeyValue {
 | 
				
			||||||
 | 
					        return this.key
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,3 +1,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const HELLO_WORLD = 'Hello, World!'
 | 
					export * from './types'
 | 
				
			||||||
 | 
					export * from './crypt/Cryptor'
 | 
				
			||||||
 | 
					export * from './crypt/AES256Cryptor'
 | 
				
			||||||
 | 
					export * from './KeyPackage'
 | 
				
			||||||
 | 
					export * from './Payload'
 | 
				
			||||||
 | 
					export * from './SharedValue'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/interface.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import {AES256Cryptor} from './crypt/AES256Cryptor'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Set of values that can be overridden externally to modify the behavior of this package.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const pkg = {
 | 
				
			||||||
 | 
					    /** Factory for producing the default Cryptor instance. */
 | 
				
			||||||
 | 
					    cryptor: AES256Cryptor.getFactory(),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { pkg }
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/** Alias for a value that might come in a Promise. */
 | 
				
			||||||
 | 
					export type Awaitable<T> = T | Promise<T>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** String that has been decrypted. */
 | 
				
			||||||
 | 
					export type DecodedValue = string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** String that has been encrypted. */
 | 
				
			||||||
 | 
					export type EncodedValue = string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** A key used for encryption. */
 | 
				
			||||||
 | 
					export type KeyValue = string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Interface that designates a particular value as able to be constructed.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export interface Instantiable<T> {
 | 
				
			||||||
 | 
					    new(...args: any[]): T
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Returns true if the given value is instantiable.
 | 
				
			||||||
 | 
					 * @param what
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function isInstantiable<T>(what: unknown): what is Instantiable<T> {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        Boolean(what)
 | 
				
			||||||
 | 
					        && (typeof what === 'object' || typeof what === 'function')
 | 
				
			||||||
 | 
					        && (what !== null)
 | 
				
			||||||
 | 
					        && 'constructor' in what
 | 
				
			||||||
 | 
					        && typeof what.constructor === 'function'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Serialized payload of a given value.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type EncodedPayload<T> = string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Determines that a given item is a valid serialized payload.
 | 
				
			||||||
 | 
					 * @param what
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function isEncodedPayload(what: unknown): what is EncodedPayload<unknown> {
 | 
				
			||||||
 | 
					    if ( typeof what !== 'string' ) {
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return what.startsWith('multicrypt:')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user