Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make both crypto implementations support sign #727

Merged
merged 10 commits into from
Jun 5, 2019
47 changes: 47 additions & 0 deletions browser-test/fixtures/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2019 Google LLC. All Rights Reserved.
*
* 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.
*/

// The following private and public keys were copied from JWK RFC 7517:
// https://tools.ietf.org/html/rfc7517
export const privateKey = {
kty: 'RSA',
n:
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
e: 'AQAB',
d:
'X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q',
p:
'83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs',
q:
'3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk',
dp:
'G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0',
dq:
's9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk',
qi:
'GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU',
alg: 'RS256',
kid: '2011-04-29',
};

export const publicKey = {
kty: 'RSA',
n:
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
e: 'AQAB',
alg: 'RS256',
kid: '2011-04-29',
};
49 changes: 33 additions & 16 deletions browser-test/test.crypto.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
/**
* Copyright 2019 Google LLC. All Rights Reserved.
*
* 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 * as base64js from 'base64-js';
import {assert} from 'chai';
import {createCrypto} from '../src/crypto/crypto';
import {BrowserCrypto} from '../src/crypto/browser/crypto';

// The following public key was copied from JWK RFC 7517:
// https://tools.ietf.org/html/rfc7517
// The private key used for signing the test message below was taken from the same RFC.
const publicKey = {
kty: 'RSA',
n:
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
e: 'AQAB',
alg: 'RS256',
kid: '2011-04-29',
};
import {privateKey, publicKey} from './fixtures/keys';

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

it('should not createSign', () => {
assert.throws(() => {
crypto.createSign('never worked');
});
it('should sign a message', async () => {
const message = 'This message is signed';
const expectedSignatureBase64 = [
'BE1qD48LdssePdMmOhcanOd8V+i4yLSOL0H2EXNyy',
'lCePnldIsLVqrOJnVkd0MUKxS/Y9B0te2tqlS8psP',
'j9IWjcpiQeT9wUDRadxHIX26W6JHgSCOzOavpJCbh',
'M3Kez7QEwbkrI54rYu7qgx/mmckxkC0vhg0Z5OQbO',
'IXfILVs1ztNNdt9r/ZzNVxTMKhL3nHLfjVqG/LUGy',
'RhFhjzLvIJAfL0CSEfycUvm6t5NVzF4SkZ8KKQ7wJ',
'vLw492bRB/633GJOZ1prVjAUQUI64BXFrvRgWsxLK',
'M0XtF5tNbC+eIDrH0LiMraAhcZwj1iWofH1h/dg3E',
'xtU9UWfbed/yfw==',
].join('');

const signatureBase64 = await crypto.sign(privateKey, message);
assert.strictEqual(signatureBase64, expectedSignatureBase64);
});

it('should decode unpadded base64', () => {
Expand Down
33 changes: 1 addition & 32 deletions browser-test/test.oauth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
* limitations under the License.
*/

/// <reference path='../node_modules/@types/sinon/ts3.1/index.d.ts'>

import * as base64js from 'base64-js';
import {assert} from 'chai';
import * as sinon from 'sinon';
import {privateKey, publicKey} from './fixtures/keys';

// Not all browsers support `TextEncoder`. The following `require` will
// provide a fast UTF8-only replacement for those browsers that don't support
Expand Down Expand Up @@ -61,36 +60,6 @@ const FEDERATED_SIGNON_JWK_CERTS_AXIOS_RESPONSE = {
},
data: {keys: FEDERATED_SIGNON_JWK_CERTS},
};
// The following private and public keys were copied from JWK RFC 7517:
// https://tools.ietf.org/html/rfc7517
const privateKey = {
kty: 'RSA',
n:
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
e: 'AQAB',
d:
'X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q',
p:
'83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs',
q:
'3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk',
dp:
'G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0',
dq:
's9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk',
qi:
'GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU',
alg: 'RS256',
kid: '2011-04-29',
};
const publicKey = {
kty: 'RSA',
n:
'0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw',
e: 'AQAB',
alg: 'RS256',
kid: '2011-04-29',
};

describe('Browser OAuth2 tests', () => {
let client: OAuth2Client;
Expand Down
7 changes: 3 additions & 4 deletions src/auth/googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,10 +779,9 @@ export class GoogleAuth {
async sign(data: string): Promise<string> {
const client = await this.getClient();
const crypto = createCrypto();
if (client instanceof JWT && client.key && !isBrowser()) {
const sign = crypto.createSign('RSA-SHA256');
sign.update(data);
return sign.sign(client.key, 'base64');
if (client instanceof JWT && client.key) {
const sign = await crypto.sign(client.key, data);
return sign;
}

const projectId = await this.getProjectId();
Expand Down
20 changes: 18 additions & 2 deletions src/crypto/browser/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,24 @@ export class BrowserCrypto implements Crypto {
return result;
}

createSign(algorithm: string): CryptoSigner {
throw new Error('createSign is not implemented in BrowserCrypto');
async sign(privateKey: JwkCertificate, data: string): Promise<string> {
const algo = {
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-256'},
};
const dataArray = new TextEncoder().encode(data);
const cryptoKey = await window.crypto.subtle.importKey(
'jwk',
privateKey,
algo,
true,
['sign']
);

// SubtleCrypto's sign method is async so we must make
// this method async as well.
const result = await window.crypto.subtle.sign(algo, cryptoKey, dataArray);
return base64js.fromByteArray(new Uint8Array(result));
}

decodeBase64StringUtf8(base64: string): string {
Expand Down
5 changes: 4 additions & 1 deletion src/crypto/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ export interface Crypto {
data: string | Buffer,
signature: string
): Promise<boolean>;
createSign(algorithm: string): CryptoSigner;
sign(
privateKey: string | JwkCertificate,
data: string | Buffer
): Promise<string>;
decodeBase64StringUtf8(base64: string): string;
encodeBase64StringUtf8(text: string): string;
}
Expand Down
8 changes: 6 additions & 2 deletions src/crypto/node/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ export class NodeCrypto implements Crypto {
): Promise<boolean> {
const verifier = crypto.createVerify('sha256');
verifier.update(data);
verifier.end();
return verifier.verify(pubkey, signature, 'base64');
}

createSign(algorithm: string): CryptoSigner {
return crypto.createSign(algorithm);
async sign(privateKey: string, data: string | Buffer): Promise<string> {
const signer = crypto.createSign('RSA-SHA256');
signer.update(data);
signer.end();
return signer.sign(privateKey, 'base64');
}

decodeBase64StringUtf8(base64: string): string {
Expand Down
7 changes: 2 additions & 5 deletions test/test.crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('Node.js crypto tests', () => {
assert(verified);
});

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

const signer = crypto.createSign('SHA256');
assert(signer);
signer.update(message);
const signatureBase64 = signer.sign(privateKey, 'base64');
const signatureBase64 = await crypto.sign(privateKey, message);
assert.strictEqual(signatureBase64, expectedSignatureBase64);
});

Expand Down