Skip to content
This repository was archived by the owner on Nov 20, 2025. It is now read-only.

Commit e445fb3

Browse files
feat: make both crypto implementations support sign (#727)
* fix: pad base64 strings for base64js * really test signer for Node * feat: implement sign for browser * fix test * gts fix * revert webpack.config.js
1 parent 2fe53ad commit e445fb3

File tree

8 files changed

+114
-62
lines changed

8 files changed

+114
-62
lines changed

browser-test/fixtures/keys.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Copyright 2019 Google LLC. All Rights Reserved.
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+
// The following private and public keys were copied from JWK RFC 7517:
18+
// https://tools.ietf.org/html/rfc7517
19+
export const privateKey = {
20+
kty: 'RSA',
21+
n:
22+
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
23+
e: 'AQAB',
24+
d:
25+
'X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q',
26+
p:
27+
'83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs',
28+
q:
29+
'3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk',
30+
dp:
31+
'G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0',
32+
dq:
33+
's9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk',
34+
qi:
35+
'GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU',
36+
alg: 'RS256',
37+
kid: '2011-04-29',
38+
};
39+
40+
export const publicKey = {
41+
kty: 'RSA',
42+
n:
43+
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
44+
e: 'AQAB',
45+
alg: 'RS256',
46+
kid: '2011-04-29',
47+
};

browser-test/test.crypto.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
/**
2+
* Copyright 2019 Google LLC. All Rights Reserved.
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+
117
import * as base64js from 'base64-js';
218
import {assert} from 'chai';
319
import {createCrypto} from '../src/crypto/crypto';
420
import {BrowserCrypto} from '../src/crypto/browser/crypto';
5-
6-
// The following public key was copied from JWK RFC 7517:
7-
// https://tools.ietf.org/html/rfc7517
8-
// The private key used for signing the test message below was taken from the same RFC.
9-
const publicKey = {
10-
kty: 'RSA',
11-
n:
12-
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
13-
e: 'AQAB',
14-
alg: 'RS256',
15-
kid: '2011-04-29',
16-
};
21+
import {privateKey, publicKey} from './fixtures/keys';
1722

1823
// Not all browsers support `TextEncoder`. The following `require` will
1924
// provide a fast UTF8-only replacement for those browsers that don't support
@@ -64,10 +69,22 @@ describe('Browser crypto tests', () => {
6469
assert(verified);
6570
});
6671

67-
it('should not createSign', () => {
68-
assert.throws(() => {
69-
crypto.createSign('never worked');
70-
});
72+
it('should sign a message', async () => {
73+
const message = 'This message is signed';
74+
const expectedSignatureBase64 = [
75+
'BE1qD48LdssePdMmOhcanOd8V+i4yLSOL0H2EXNyy',
76+
'lCePnldIsLVqrOJnVkd0MUKxS/Y9B0te2tqlS8psP',
77+
'j9IWjcpiQeT9wUDRadxHIX26W6JHgSCOzOavpJCbh',
78+
'M3Kez7QEwbkrI54rYu7qgx/mmckxkC0vhg0Z5OQbO',
79+
'IXfILVs1ztNNdt9r/ZzNVxTMKhL3nHLfjVqG/LUGy',
80+
'RhFhjzLvIJAfL0CSEfycUvm6t5NVzF4SkZ8KKQ7wJ',
81+
'vLw492bRB/633GJOZ1prVjAUQUI64BXFrvRgWsxLK',
82+
'M0XtF5tNbC+eIDrH0LiMraAhcZwj1iWofH1h/dg3E',
83+
'xtU9UWfbed/yfw==',
84+
].join('');
85+
86+
const signatureBase64 = await crypto.sign(privateKey, message);
87+
assert.strictEqual(signatureBase64, expectedSignatureBase64);
7188
});
7289

7390
it('should decode unpadded base64', () => {

browser-test/test.oauth2.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
/// <reference path='../node_modules/@types/sinon/ts3.1/index.d.ts'>
18-
1917
import * as base64js from 'base64-js';
2018
import {assert} from 'chai';
2119
import * as sinon from 'sinon';
20+
import {privateKey, publicKey} from './fixtures/keys';
2221

2322
// Not all browsers support `TextEncoder`. The following `require` will
2423
// provide a fast UTF8-only replacement for those browsers that don't support
@@ -61,36 +60,6 @@ const FEDERATED_SIGNON_JWK_CERTS_AXIOS_RESPONSE = {
6160
},
6261
data: {keys: FEDERATED_SIGNON_JWK_CERTS},
6362
};
64-
// The following private and public keys were copied from JWK RFC 7517:
65-
// https://tools.ietf.org/html/rfc7517
66-
const privateKey = {
67-
kty: 'RSA',
68-
n:
69-
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
70-
e: 'AQAB',
71-
d:
72-
'X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q',
73-
p:
74-
'83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs',
75-
q:
76-
'3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk',
77-
dp:
78-
'G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0',
79-
dq:
80-
's9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk',
81-
qi:
82-
'GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU',
83-
alg: 'RS256',
84-
kid: '2011-04-29',
85-
};
86-
const publicKey = {
87-
kty: 'RSA',
88-
n:
89-
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
90-
e: 'AQAB',
91-
alg: 'RS256',
92-
kid: '2011-04-29',
93-
};
9463

9564
describe('Browser OAuth2 tests', () => {
9665
let client: OAuth2Client;

src/auth/googleauth.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -779,10 +779,9 @@ export class GoogleAuth {
779779
async sign(data: string): Promise<string> {
780780
const client = await this.getClient();
781781
const crypto = createCrypto();
782-
if (client instanceof JWT && client.key && !isBrowser()) {
783-
const sign = crypto.createSign('RSA-SHA256');
784-
sign.update(data);
785-
return sign.sign(client.key, 'base64');
782+
if (client instanceof JWT && client.key) {
783+
const sign = await crypto.sign(client.key, data);
784+
return sign;
786785
}
787786

788787
const projectId = await this.getProjectId();

src/crypto/browser/crypto.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,24 @@ export class BrowserCrypto implements Crypto {
107107
return result;
108108
}
109109

110-
createSign(algorithm: string): CryptoSigner {
111-
throw new Error('createSign is not implemented in BrowserCrypto');
110+
async sign(privateKey: JwkCertificate, data: string): Promise<string> {
111+
const algo = {
112+
name: 'RSASSA-PKCS1-v1_5',
113+
hash: {name: 'SHA-256'},
114+
};
115+
const dataArray = new TextEncoder().encode(data);
116+
const cryptoKey = await window.crypto.subtle.importKey(
117+
'jwk',
118+
privateKey,
119+
algo,
120+
true,
121+
['sign']
122+
);
123+
124+
// SubtleCrypto's sign method is async so we must make
125+
// this method async as well.
126+
const result = await window.crypto.subtle.sign(algo, cryptoKey, dataArray);
127+
return base64js.fromByteArray(new Uint8Array(result));
112128
}
113129

114130
decodeBase64StringUtf8(base64: string): string {

src/crypto/crypto.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export interface Crypto {
4747
data: string | Buffer,
4848
signature: string
4949
): Promise<boolean>;
50-
createSign(algorithm: string): CryptoSigner;
50+
sign(
51+
privateKey: string | JwkCertificate,
52+
data: string | Buffer
53+
): Promise<string>;
5154
decodeBase64StringUtf8(base64: string): string;
5255
encodeBase64StringUtf8(text: string): string;
5356
}

src/crypto/node/crypto.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ export class NodeCrypto implements Crypto {
3636
): Promise<boolean> {
3737
const verifier = crypto.createVerify('sha256');
3838
verifier.update(data);
39+
verifier.end();
3940
return verifier.verify(pubkey, signature, 'base64');
4041
}
4142

42-
createSign(algorithm: string): CryptoSigner {
43-
return crypto.createSign(algorithm);
43+
async sign(privateKey: string, data: string | Buffer): Promise<string> {
44+
const signer = crypto.createSign('RSA-SHA256');
45+
signer.update(data);
46+
signer.end();
47+
return signer.sign(privateKey, 'base64');
4448
}
4549

4650
decodeBase64StringUtf8(base64: string): string {

test/test.crypto.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('Node.js crypto tests', () => {
4747
assert(verified);
4848
});
4949

50-
it('should create a signer that works', () => {
50+
it('should sign a message', async () => {
5151
const message = 'This message is signed';
5252
const expectedSignatureBase64 = [
5353
'ufyKBV+Ar7Yq8CSmSIN9m38ch4xnWBz8CP4qHh6V+',
@@ -57,10 +57,7 @@ describe('Node.js crypto tests', () => {
5757
'bP28XNU=',
5858
].join('');
5959

60-
const signer = crypto.createSign('SHA256');
61-
assert(signer);
62-
signer.update(message);
63-
const signatureBase64 = signer.sign(privateKey, 'base64');
60+
const signatureBase64 = await crypto.sign(privateKey, message);
6461
assert.strictEqual(signatureBase64, expectedSignatureBase64);
6562
});
6663

0 commit comments

Comments
 (0)