diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index a55ced9a..49437e4d 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -27,6 +27,7 @@ import {LogVerbosity, Status} from './constants'; import * as logging from './logging'; import {Deserialize, loadPackageDefinition, makeClientConstructor, Serialize} from './make-client'; import {Metadata} from './metadata'; +import {KeyCertPair, ServerCredentials} from './server-credentials'; import {StatusBuilder} from './status-builder'; const supportedNodeVersions = '^8.11.2 || >=9.4'; @@ -226,15 +227,9 @@ export const Server = (options: any) => { throw new Error('Not yet implemented'); }; -export const ServerCredentials = { - createSsl: - (rootCerts: any, keyCertPairs: any, checkClientCertificate: any) => { - throw new Error('Not yet implemented'); - }, - createInsecure: () => { - throw new Error('Not yet implemented'); - } -}; +export {ServerCredentials}; +export {KeyCertPair}; + export const getClientChannel = (client: Client) => { return Client.prototype.getChannel.call(client); diff --git a/packages/grpc-js/src/server-credentials.ts b/packages/grpc-js/src/server-credentials.ts new file mode 100644 index 00000000..9e5dc782 --- /dev/null +++ b/packages/grpc-js/src/server-credentials.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2019 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import {SecureServerOptions} from 'http2'; + + +export type KeyCertPair = { + private_key: Buffer, + cert_chain: Buffer +}; + + +export abstract class ServerCredentials { + abstract _isSecure(): boolean; + abstract _getSettings(): SecureServerOptions|null; + + static createInsecure(): ServerCredentials { + return new InsecureServerCredentials(); + } + + static createSsl( + rootCerts: Buffer|null, keyCertPairs: KeyCertPair[], + checkClientCertificate = false): ServerCredentials { + if (rootCerts !== null && !Buffer.isBuffer(rootCerts)) { + throw new TypeError('rootCerts must be null or a Buffer'); + } + + if (!Array.isArray(keyCertPairs)) { + throw new TypeError('keyCertPairs must be an array'); + } + + if (typeof checkClientCertificate !== 'boolean') { + throw new TypeError('checkClientCertificate must be a boolean'); + } + + const cert = []; + const key = []; + + for (let i = 0; i < keyCertPairs.length; i++) { + const pair = keyCertPairs[i]; + + if (pair === null || typeof pair !== 'object') { + throw new TypeError(`keyCertPair[${i}] must be an object`); + } + + if (!Buffer.isBuffer(pair.private_key)) { + throw new TypeError(`keyCertPair[${i}].private_key must be a Buffer`); + } + + if (!Buffer.isBuffer(pair.cert_chain)) { + throw new TypeError(`keyCertPair[${i}].cert_chain must be a Buffer`); + } + + cert.push(pair.cert_chain); + key.push(pair.private_key); + } + + return new SecureServerCredentials({ + ca: rootCerts || undefined, + cert, + key, + requestCert: checkClientCertificate + }); + } +} + + +class InsecureServerCredentials extends ServerCredentials { + _isSecure(): boolean { + return false; + } + + _getSettings(): null { + return null; + } +} + + +class SecureServerCredentials extends ServerCredentials { + private options: SecureServerOptions; + + constructor(options: SecureServerOptions) { + super(); + this.options = options; + } + + _isSecure(): boolean { + return true; + } + + _getSettings(): SecureServerOptions { + return this.options; + } +} diff --git a/packages/grpc-js/test/test-server-credentials.ts b/packages/grpc-js/test/test-server-credentials.ts new file mode 100644 index 00000000..847b647a --- /dev/null +++ b/packages/grpc-js/test/test-server-credentials.ts @@ -0,0 +1,126 @@ +/* + * Copyright 2019 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Allow `any` data type for testing runtime type checking. +// tslint:disable no-any +import * as assert from 'assert'; +import {readFileSync} from 'fs'; +import {join} from 'path'; +import {ServerCredentials} from '../src'; + +const ca = readFileSync(join(__dirname, 'fixtures', 'ca.pem')); +const key = readFileSync(join(__dirname, 'fixtures', 'server1.key')); +const cert = readFileSync(join(__dirname, 'fixtures', 'server1.pem')); + +describe('Server Credentials', () => { + describe('createInsecure', () => { + it('creates insecure credentials', () => { + const creds = ServerCredentials.createInsecure(); + + assert.strictEqual(creds._isSecure(), false); + assert.strictEqual(creds._getSettings(), null); + }); + }); + + describe('createSsl', () => { + it('accepts a buffer and array as the first two arguments', () => { + const creds = ServerCredentials.createSsl(ca, []); + + assert.strictEqual(creds._isSecure(), true); + assert.deepStrictEqual( + creds._getSettings(), {ca, cert: [], key: [], requestCert: false}); + }); + + it('accepts a boolean as the third argument', () => { + const creds = ServerCredentials.createSsl(ca, [], true); + + assert.strictEqual(creds._isSecure(), true); + assert.deepStrictEqual( + creds._getSettings(), {ca, cert: [], key: [], requestCert: true}); + }); + + it('accepts an object with two buffers in the second argument', () => { + const keyCertPairs = [{private_key: key, cert_chain: cert}]; + const creds = ServerCredentials.createSsl(null, keyCertPairs); + + assert.strictEqual(creds._isSecure(), true); + assert.deepStrictEqual( + creds._getSettings(), + {ca: undefined, cert: [cert], key: [key], requestCert: false}); + }); + + it('accepts multiple objects in the second argument', () => { + const keyCertPairs = [ + {private_key: key, cert_chain: cert}, + {private_key: key, cert_chain: cert} + ]; + const creds = ServerCredentials.createSsl(null, keyCertPairs, false); + + assert.strictEqual(creds._isSecure(), true); + assert.deepStrictEqual(creds._getSettings(), { + ca: undefined, + cert: [cert, cert], + key: [key, key], + requestCert: false + }); + }); + + it('fails if the second argument is not an Array', () => { + assert.throws(() => { + ServerCredentials.createSsl(ca, 'test' as any); + }, /TypeError: keyCertPairs must be an array/); + }); + + it('fails if the first argument is a non-Buffer value', () => { + assert.throws(() => { + ServerCredentials.createSsl('test' as any, []); + }, /TypeError: rootCerts must be null or a Buffer/); + }); + + it('fails if the third argument is a non-boolean value', () => { + assert.throws(() => { + ServerCredentials.createSsl(ca, [], 'test' as any); + }, /TypeError: checkClientCertificate must be a boolean/); + }); + + it('fails if the array elements are not objects', () => { + assert.throws(() => { + ServerCredentials.createSsl(ca, ['test'] as any); + }, /TypeError: keyCertPair\[0\] must be an object/); + + assert.throws(() => { + ServerCredentials.createSsl(ca, [null] as any); + }, /TypeError: keyCertPair\[0\] must be an object/); + }); + + it('fails if the object does not have a Buffer private key', () => { + const keyCertPairs: any = [{private_key: 'test', cert_chain: cert}]; + + assert.throws(() => { + ServerCredentials.createSsl(null, keyCertPairs); + }, /TypeError: keyCertPair\[0\].private_key must be a Buffer/); + }); + + it('fails if the object does not have a Buffer cert chain', () => { + const keyCertPairs: any = [{private_key: key, cert_chain: 'test'}]; + + assert.throws(() => { + ServerCredentials.createSsl(null, keyCertPairs); + }, /TypeError: keyCertPair\[0\].cert_chain must be a Buffer/); + }); + }); +});