Skip to content

Commit 40eef7a

Browse files
committed
grpc-js: add ServerCredentials support
This commit adds ServerCredentials to the pure JS implementation.
1 parent f345593 commit 40eef7a

File tree

3 files changed

+238
-9
lines changed

3 files changed

+238
-9
lines changed

packages/grpc-js/src/index.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {LogVerbosity, Status} from './constants';
2727
import * as logging from './logging';
2828
import {Deserialize, loadPackageDefinition, makeClientConstructor, Serialize} from './make-client';
2929
import {Metadata} from './metadata';
30+
import {KeyCertPair, ServerCredentials} from './server-credentials';
3031
import {StatusBuilder} from './status-builder';
3132

3233
const supportedNodeVersions = '^8.11.2 || >=9.4';
@@ -226,15 +227,9 @@ export const Server = (options: any) => {
226227
throw new Error('Not yet implemented');
227228
};
228229

229-
export const ServerCredentials = {
230-
createSsl:
231-
(rootCerts: any, keyCertPairs: any, checkClientCertificate: any) => {
232-
throw new Error('Not yet implemented');
233-
},
234-
createInsecure: () => {
235-
throw new Error('Not yet implemented');
236-
}
237-
};
230+
export {ServerCredentials};
231+
export {KeyCertPair};
232+
238233

239234
export const getClientChannel = (client: Client) => {
240235
return Client.prototype.getChannel.call(client);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2019 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
import {SecureServerOptions} from 'http2';
19+
20+
21+
export type KeyCertPair = {
22+
private_key: Buffer,
23+
cert_chain: Buffer
24+
};
25+
26+
27+
export abstract class ServerCredentials {
28+
abstract _isSecure(): boolean;
29+
abstract _getSettings(): SecureServerOptions|null;
30+
31+
static createInsecure(): ServerCredentials {
32+
return new InsecureServerCredentials();
33+
}
34+
35+
static createSsl(
36+
rootCerts: Buffer|null, keyCertPairs: KeyCertPair[],
37+
checkClientCertificate = false): ServerCredentials {
38+
if (rootCerts !== null && !Buffer.isBuffer(rootCerts)) {
39+
throw new TypeError('rootCerts must be null or a Buffer');
40+
}
41+
42+
if (!Array.isArray(keyCertPairs)) {
43+
throw new TypeError('keyCertPairs must be an array');
44+
}
45+
46+
if (typeof checkClientCertificate !== 'boolean') {
47+
throw new TypeError('checkClientCertificate must be a boolean');
48+
}
49+
50+
const cert = [];
51+
const key = [];
52+
53+
for (let i = 0; i < keyCertPairs.length; i++) {
54+
const pair = keyCertPairs[i];
55+
56+
if (pair === null || typeof pair !== 'object') {
57+
throw new TypeError(`keyCertPair[${i}] must be an object`);
58+
}
59+
60+
if (!Buffer.isBuffer(pair.private_key)) {
61+
throw new TypeError(`keyCertPair[${i}].private_key must be a Buffer`);
62+
}
63+
64+
if (!Buffer.isBuffer(pair.cert_chain)) {
65+
throw new TypeError(`keyCertPair[${i}].cert_chain must be a Buffer`);
66+
}
67+
68+
cert.push(pair.cert_chain);
69+
key.push(pair.private_key);
70+
}
71+
72+
return new SecureServerCredentials({
73+
ca: rootCerts || undefined,
74+
cert,
75+
key,
76+
requestCert: checkClientCertificate
77+
});
78+
}
79+
}
80+
81+
82+
class InsecureServerCredentials extends ServerCredentials {
83+
_isSecure(): boolean {
84+
return false;
85+
}
86+
87+
_getSettings(): null {
88+
return null;
89+
}
90+
}
91+
92+
93+
class SecureServerCredentials extends ServerCredentials {
94+
private options: SecureServerOptions;
95+
96+
constructor(options: SecureServerOptions) {
97+
super();
98+
this.options = options;
99+
}
100+
101+
_isSecure(): boolean {
102+
return true;
103+
}
104+
105+
_getSettings(): SecureServerOptions {
106+
return this.options;
107+
}
108+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2019 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
// Allow `any` data type for testing runtime type checking.
19+
// tslint:disable no-any
20+
import * as assert from 'assert';
21+
import {readFileSync} from 'fs';
22+
import {join} from 'path';
23+
import {ServerCredentials} from '../src';
24+
25+
const ca = readFileSync(join(__dirname, 'fixtures', 'ca.pem'));
26+
const key = readFileSync(join(__dirname, 'fixtures', 'server1.key'));
27+
const cert = readFileSync(join(__dirname, 'fixtures', 'server1.pem'));
28+
29+
describe('Server Credentials', () => {
30+
describe('createInsecure', () => {
31+
it('creates insecure credentials', () => {
32+
const creds = ServerCredentials.createInsecure();
33+
34+
assert.strictEqual(creds._isSecure(), false);
35+
assert.strictEqual(creds._getSettings(), null);
36+
});
37+
});
38+
39+
describe('createSsl', () => {
40+
it('accepts a buffer and array as the first two arguments', () => {
41+
const creds = ServerCredentials.createSsl(ca, []);
42+
43+
assert.strictEqual(creds._isSecure(), true);
44+
assert.deepStrictEqual(
45+
creds._getSettings(), {ca, cert: [], key: [], requestCert: false});
46+
});
47+
48+
it('accepts a boolean as the third argument', () => {
49+
const creds = ServerCredentials.createSsl(ca, [], true);
50+
51+
assert.strictEqual(creds._isSecure(), true);
52+
assert.deepStrictEqual(
53+
creds._getSettings(), {ca, cert: [], key: [], requestCert: true});
54+
});
55+
56+
it('accepts an object with two buffers in the second argument', () => {
57+
const keyCertPairs = [{private_key: key, cert_chain: cert}];
58+
const creds = ServerCredentials.createSsl(null, keyCertPairs);
59+
60+
assert.strictEqual(creds._isSecure(), true);
61+
assert.deepStrictEqual(
62+
creds._getSettings(),
63+
{ca: undefined, cert: [cert], key: [key], requestCert: false});
64+
});
65+
66+
it('accepts multiple objects in the second argument', () => {
67+
const keyCertPairs = [
68+
{private_key: key, cert_chain: cert},
69+
{private_key: key, cert_chain: cert}
70+
];
71+
const creds = ServerCredentials.createSsl(null, keyCertPairs, false);
72+
73+
assert.strictEqual(creds._isSecure(), true);
74+
assert.deepStrictEqual(creds._getSettings(), {
75+
ca: undefined,
76+
cert: [cert, cert],
77+
key: [key, key],
78+
requestCert: false
79+
});
80+
});
81+
82+
it('fails if the second argument is not an Array', () => {
83+
assert.throws(() => {
84+
ServerCredentials.createSsl(ca, 'test' as any);
85+
}, /TypeError: keyCertPairs must be an array/);
86+
});
87+
88+
it('fails if the first argument is a non-Buffer value', () => {
89+
assert.throws(() => {
90+
ServerCredentials.createSsl('test' as any, []);
91+
}, /TypeError: rootCerts must be null or a Buffer/);
92+
});
93+
94+
it('fails if the third argument is a non-boolean value', () => {
95+
assert.throws(() => {
96+
ServerCredentials.createSsl(ca, [], 'test' as any);
97+
}, /TypeError: checkClientCertificate must be a boolean/);
98+
});
99+
100+
it('fails if the array elements are not objects', () => {
101+
assert.throws(() => {
102+
ServerCredentials.createSsl(ca, ['test'] as any);
103+
}, /TypeError: keyCertPair\[0\] must be an object/);
104+
105+
assert.throws(() => {
106+
ServerCredentials.createSsl(ca, [null] as any);
107+
}, /TypeError: keyCertPair\[0\] must be an object/);
108+
});
109+
110+
it('fails if the object does not have a Buffer private key', () => {
111+
const keyCertPairs: any = [{private_key: 'test', cert_chain: cert}];
112+
113+
assert.throws(() => {
114+
ServerCredentials.createSsl(null, keyCertPairs);
115+
}, /TypeError: keyCertPair\[0\].private_key must be a Buffer/);
116+
});
117+
118+
it('fails if the object does not have a Buffer cert chain', () => {
119+
const keyCertPairs: any = [{private_key: key, cert_chain: 'test'}];
120+
121+
assert.throws(() => {
122+
ServerCredentials.createSsl(null, keyCertPairs);
123+
}, /TypeError: keyCertPair\[0\].cert_chain must be a Buffer/);
124+
});
125+
});
126+
});

0 commit comments

Comments
 (0)