Skip to content

Commit

Permalink
Merge pull request #348 from Microsoft/tomlm/botconfig
Browse files Browse the repository at this point in the history
update crypto algo based on security review
  • Loading branch information
Tom Laird-McConnell committed Aug 18, 2018
2 parents 080f23c + 2dd239e commit 0a93d0a
Show file tree
Hide file tree
Showing 22 changed files with 461 additions and 162 deletions.
13 changes: 13 additions & 0 deletions libraries/botframework-config/lib/models/appInsightsService.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IAppInsightsService, ServiceTypes } from '../schema';
import { ConnectedService } from './connectedService';
export declare class AppInsightsService extends ConnectedService implements IAppInsightsService {
readonly type: ServiceTypes;
tenantId: string;
subscriptionId: string;
resourceGroup: string;
instrumentationKey: string;
constructor(source?: IAppInsightsService);
toJSON(): IAppInsightsService;
encrypt(secret: string): void;
decrypt(secret: string): void;
}
37 changes: 37 additions & 0 deletions libraries/botframework-config/lib/models/appInsightsService.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions libraries/botframework-config/lib/models/azureStorageService.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IAzureStorageService, ServiceTypes } from '../schema';
import { ConnectedService } from './connectedService';
export declare class AzureStorageService extends ConnectedService implements IAzureStorageService {
readonly type: ServiceTypes;
tenantId: string;
subscriptionId: string;
resourceGroup: string;
connectionString: string;
constructor(source?: IAzureStorageService);
toJSON(): IAzureStorageService;
encrypt(secret: string): void;
decrypt(secret: string): void;
}
37 changes: 37 additions & 0 deletions libraries/botframework-config/lib/models/azureStorageService.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 20 additions & 7 deletions libraries/botframework-config/src/botConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import * as path from 'path';
import * as process from 'process';
import * as txtfile from 'read-text-file';
import * as uuid from 'uuid';
import { decryptString, encryptString } from './encrypt';
import * as encrypt from './encrypt';
import { AzureBotService, ConnectedService, DispatchService, EndpointService, FileService, LuisService, QnaMakerService } from './models';
import { IAzureBotService, IBotConfiguration, IConnectedService, IDispatchService, IEndpointService, IFileService, ILuisService, IQnAService, ServiceTypes } from './schema';
import { AppInsightsService } from './models/appInsightsService';
import { AzureStorageService } from './models/azureStorageService';
import { IAppInsightsService, IAzureStorageService, IBotConfiguration, IConnectedService, IDispatchService, IEndpointService, IFileService, ILuisService, IQnAService, ServiceTypes } from './schema';


interface internalBotConfig {
Expand Down Expand Up @@ -135,6 +137,11 @@ export class BotConfiguration implements Partial<IBotConfiguration> {
this.services.push(BotConfiguration.serviceFromJSON(newService));
}

// Generate a key for encryption
public static generateKey(): string {
return encrypt.generateKey();
}

// encrypt all values in the config
public encrypt(secret: string) {
this.validateSecretKey(secret);
Expand All @@ -158,7 +165,7 @@ export class BotConfiguration implements Partial<IBotConfiguration> {

// legacy decryption
this.secretKey = this.legacyDecrypt(this.secretKey, secret);
this.secretKey = encryptString(this.secretKey, secret);
this.secretKey = "";

let encryptedProperties: { [key: string]: string[]; } = {
abs: [],
Expand Down Expand Up @@ -221,10 +228,10 @@ export class BotConfiguration implements Partial<IBotConfiguration> {
try {
if (!this.secretKey || this.secretKey.length == 0) {
// if no key, create a guid and enrypt that to use as secret validator
this.secretKey = encryptString(uuid(), secret);
this.secretKey = encrypt.encryptString(uuid(), secret);
} else {
// validate we can decrypt the secretKey, this tells us we have the correct secret for the rest of the file.
decryptString(this.secretKey, secret);
encrypt.decryptString(this.secretKey, secret);
}
} catch (ex) {
throw new Error('You are attempting to perform an operation which needs access to the secret and --secret is incorrect.');
Expand All @@ -251,15 +258,21 @@ export class BotConfiguration implements Partial<IBotConfiguration> {
case ServiceTypes.Dispatch:
return new DispatchService(<IDispatchService>service);

case ServiceTypes.AzureBotService:
return new AzureBotService(<IAzureBotService>service);
case ServiceTypes.AzureBot:
return new AzureBotService(<IAppInsightsService>service);

case ServiceTypes.Luis:
return new LuisService(<ILuisService>service);

case ServiceTypes.Endpoint:
return new EndpointService(<IEndpointService>service);

case ServiceTypes.AppInsights:
return new AppInsightsService(<IAppInsightsService>service);

case ServiceTypes.AzureStorage:
return new AzureStorageService(<IAzureStorageService>service);

default:
throw new TypeError(`${service.type} is not a known service implementation.`);
}
Expand Down
51 changes: 28 additions & 23 deletions libraries/botframework-config/src/encrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,52 @@

import * as crypto from 'crypto';

export function generateKey(): string {
return crypto.randomBytes(32).toString('base64');
}

/**
* Encrypt a string using standardized encyryption of AES256 with
* key = SHA256 hash of UTF8 secret
* iv = optional 16 bytes of iv string
* Encrypt a string using standardized encyryption of AES256
* @param value value to encrypt
* @param secret secret to use
* @param iv optional salt to value to make unique. Only needed if value could be the same
* @param key secret to use
*/
export function encryptString(value: string, secret: string, iv?: string): string {
// standardized encyryption is AES256 using
// key = SHA256 hash of UTF8 secret
// iv = optional 16 bytes of iv string
export function encryptString(value: string, key: string): string {
if (!value || value.length == 0)
throw new Error('you must pass a value');
if (!secret || value.length == 0)
throw new Error('you must pass a secret');
let secretKey = crypto.createHash('sha256').update(secret, "utf8").digest();
let ivKey = Buffer.from(((iv || '').toLowerCase() + ' ').substr(0, 16), "utf8");
let cipher = crypto.createCipheriv('aes256', secretKey, ivKey);

if (!key || value.length == 0)
throw new Error('you must pass a key');

let keyBytes = new Buffer(key, 'base64');
let ivKey = crypto.randomBytes(16);
let cipher = crypto.createCipheriv('aes256', keyBytes, ivKey);
let encryptedValue = cipher.update(value, 'utf8', 'base64');
encryptedValue += cipher.final('base64');
return encryptedValue;
return `${ivKey.toString('base64')}!${encryptedValue}`;
}

/**
* Decrypt a string using standardized encyryption of AES256 with
* key = SHA256 hash of UTF8 secret
* iv = optional 16 bytes of iv string
* @param enryptedValue value to decrypt
* @param secret secret to use
* @param iv optional salt to value to make unique. Only needed if value could be the same
* @param key secret to use
*/
export function decryptString(encryptedValue: string, secret: string, iv?: string): string {
export function decryptString(encryptedValue: string, key: string): string {
if (!encryptedValue || encryptedValue.length == 0)
throw new Error('you must pass a encryptedValue');
if (!secret || secret.length == 0)

if (!key || key.length == 0)
throw new Error('you must pass a secret');
let secretKey = crypto.createHash('sha256').update(secret || '', "utf8").digest();
let ivKey = Buffer.from(((iv || '').toLowerCase() + ' ').substr(0, 16), "utf8");
let decipher = crypto.createDecipheriv('aes256', secretKey, ivKey);
let value = decipher.update(encryptedValue, 'base64', 'utf8');

let parts = encryptedValue.split('!');
if (parts.length != 2)
throw new Error("The enrypted value is not a valid format");

let ivBytes = new Buffer(parts[0], 'base64');
let keyBytes = new Buffer(key, 'base64');
let decipher = crypto.createDecipheriv('aes256', keyBytes, ivBytes);
let value = decipher.update(parts[1], 'base64', 'utf8');
value += decipher.final('utf8');
return value;
}
2 changes: 1 addition & 1 deletion libraries/botframework-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
* Licensed under the MIT License.
*/
export { BotConfiguration } from './botConfiguration';
export { decryptString, encryptString } from './encrypt';
export { decryptString, encryptString, generateKey } from './encrypt';
export * from './models';
export * from './schema';
38 changes: 38 additions & 0 deletions libraries/botframework-config/src/models/appInsightsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/
import { decryptString, encryptString } from '../encrypt';
import { IAppInsightsService, ServiceTypes } from '../schema';
import { ConnectedService } from './connectedService';

export class AppInsightsService extends ConnectedService implements IAppInsightsService {
public readonly type = ServiceTypes.AppInsights;
public tenantId = '';
public subscriptionId = '';
public resourceGroup = '';
public instrumentationKey = '';

constructor(source: IAppInsightsService = {} as IAppInsightsService) {
super(source);
const { tenantId = '', subscriptionId = '', resourceGroup = '', instrumentationKey = '' } = source;
Object.assign(this, { tenantId, subscriptionId, resourceGroup, instrumentationKey });
}

public toJSON(): IAppInsightsService {
let { id, name, tenantId, subscriptionId, resourceGroup, instrumentationKey } = this;
return { type: ServiceTypes.AppInsights, id, name, tenantId, subscriptionId, resourceGroup, instrumentationKey };
}

// encrypt keys in service
public encrypt(secret: string): void {
if (this.instrumentationKey && this.instrumentationKey.length > 0)
this.instrumentationKey = encryptString(this.instrumentationKey, secret);
}

// decrypt keys in service
public decrypt(secret: string): void {
if (this.instrumentationKey && this.instrumentationKey.length > 0)
this.instrumentationKey = decryptString(this.instrumentationKey, secret);
}
}
4 changes: 2 additions & 2 deletions libraries/botframework-config/src/models/azureBotService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IAzureBotService, ServiceTypes } from '../schema';
import { ConnectedService } from './connectedService';

export class AzureBotService extends ConnectedService implements IAzureBotService {
public readonly type = ServiceTypes.AzureBotService;
public readonly type = ServiceTypes.AzureBot;
public tenantId = '';
public subscriptionId = '';
public resourceGroup = '';
Expand All @@ -19,7 +19,7 @@ export class AzureBotService extends ConnectedService implements IAzureBotServic

public toJSON(): IAzureBotService {
let { id, name, tenantId, subscriptionId, resourceGroup } = this;
return { type: ServiceTypes.AzureBotService, id, name, tenantId, subscriptionId, resourceGroup };
return { type: ServiceTypes.AzureBot, id, name, tenantId, subscriptionId, resourceGroup };
}

// encrypt keys in service
Expand Down
38 changes: 38 additions & 0 deletions libraries/botframework-config/src/models/azureStorageService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/
import { decryptString, encryptString } from '../encrypt';
import { IAzureStorageService, ServiceTypes } from '../schema';
import { ConnectedService } from './connectedService';

export class AzureStorageService extends ConnectedService implements IAzureStorageService {
public readonly type = ServiceTypes.AzureStorage;
public tenantId = '';
public subscriptionId = '';
public resourceGroup = '';
public connectionString = '';

constructor(source: IAzureStorageService = {} as IAzureStorageService) {
super(source);
const { tenantId = '', subscriptionId = '', resourceGroup = '', connectionString='' } = source;
Object.assign(this, { tenantId, subscriptionId, resourceGroup, connectionString });
}

public toJSON(): IAzureStorageService {
let { id, name, tenantId, subscriptionId, resourceGroup, connectionString } = this;
return { type: ServiceTypes.AzureStorage, id, name, tenantId, subscriptionId, resourceGroup, connectionString };
}

// encrypt keys in service
public encrypt(secret: string): void {
if (this.connectionString && this.connectionString.length > 0)
this.connectionString = encryptString(this.connectionString, secret);
}

// decrypt keys in service
public decrypt(secret: string): void {
if (this.connectionString && this.connectionString.length > 0)
this.connectionString = decryptString(this.connectionString, secret);
}
}
4 changes: 2 additions & 2 deletions libraries/botframework-config/src/models/connectedService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export abstract class ConnectedService implements IConnectedService {
public abstract toJSON(): IConnectedService;

// encrypt keys in service
public abstract encrypt(secret:string, iv?:string): void ;
public abstract encrypt(secret:string): void ;

// decrypt keys in service
public abstract decrypt(secret:string, iv?:string): void ;
public abstract decrypt(secret:string): void ;

}
8 changes: 4 additions & 4 deletions libraries/botframework-config/src/models/dispatchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ export class DispatchService extends LuisService implements IDispatchService {
constructor(source: IDispatchService = {} as IDispatchService) {
super(source);
this.type = ServiceTypes.Dispatch;
const { appId = '', authoringKey = '', serviceIds = [], subscriptionKey = '', version = '' } = source;
const { appId = '', authoringKey = '', serviceIds = [], subscriptionKey = '', version = '', region = '' } = source;
this.id = appId;
Object.assign(this, { appId, authoringKey, serviceIds, subscriptionKey, version });
Object.assign(this, { appId, authoringKey, serviceIds, subscriptionKey, version, region });
}

public toJSON(): IDispatchService {
const { appId, authoringKey, name, serviceIds, subscriptionKey, version } = this;
return { type: ServiceTypes.Dispatch, id: appId, name, appId, authoringKey, serviceIds, subscriptionKey, version };
const { appId, authoringKey, name, serviceIds, subscriptionKey, version, region } = this;
return { type: ServiceTypes.Dispatch, id: appId, name, appId, authoringKey, serviceIds, subscriptionKey, version, region };
}
}

0 comments on commit 0a93d0a

Please sign in to comment.