Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions googleapis_auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 2.2.1-wip

## 2.2.0

- Added `quotaProject` support to existing credentials classes
Expand Down
4 changes: 2 additions & 2 deletions googleapis_auth/lib/src/crypto/asn1.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'dart:typed_data';

import 'package:meta/meta.dart';

import 'rsa.dart';
import 'rsa.dart' as rsa;

// ignore: avoid_classes_with_only_static_members
abstract final class ASN1Parser {
Expand Down Expand Up @@ -91,7 +91,7 @@ abstract final class ASN1Parser {
switch (tag) {
case integerTag:
final size = readEncodedLength();
return ASN1Integer._(RSAAlgorithm.bytes2BigInt(readBytes(size)));
return ASN1Integer._(rsa.bytes2BigInt(readBytes(size)));
case octetStringTag:
final size = readEncodedLength();
return ASN1OctetString._(readBytes(size));
Expand Down
87 changes: 42 additions & 45 deletions googleapis_auth/lib/src/crypto/rsa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import 'dart:typed_data';

import 'package:meta/meta.dart';

/// Represents integers obtained while creating a Public/Private key pair.
final class RSAPrivateKey {
/// First prime number.
Expand Down Expand Up @@ -50,57 +52,52 @@ final class RSAPrivateKey {
);
}

/// Provides a [rawSign] method for signing messages with a [RSAPrivateKey].
// ignore: avoid_classes_with_only_static_members
abstract final class RSAAlgorithm {
/// Performs the private key operation (signing) on [bytes] with the private
/// [key].
///
/// Others who have access to the public key will be able to verify this
/// the result.
///
/// The [intendedLength] argument specifies the number of bytes in which the
/// result should be encoded. Zero bytes will be used for padding.
static Uint8List rawSign(
RSAPrivateKey key,
List<int> bytes,
int intendedLength,
) {
final message = bytes2BigInt(bytes);
final encryptedMessage = _encryptInteger(key, message);
return integer2Bytes(encryptedMessage, intendedLength);
}
/// Performs the private key operation (signing) on [bytes] with the private
/// [key].
///
/// Others who have access to the public key will be able to verify this
/// the result.
///
/// The [intendedLength] argument specifies the number of bytes in which the
/// result should be encoded. Zero bytes will be used for padding.
@internal
Uint8List rawSign(RSAPrivateKey key, List<int> bytes, int intendedLength) {
final message = bytes2BigInt(bytes);
final encryptedMessage = _encryptInteger(key, message);
return integer2Bytes(encryptedMessage, intendedLength);
}

static BigInt _encryptInteger(RSAPrivateKey key, BigInt x) {
// The following is equivalent to `(x % key.n).modPow(key.d, key.n)` but is
// much more efficient. It exploits the fact that we have dmp1/dmq1.
var xp = (x % key.p).modPow(key.dmp1, key.p);
final xq = (x % key.q).modPow(key.dmq1, key.q);
while (xp < xq) {
xp += key.p;
}
return ((((xp - xq) * key.coeff) % key.p) * key.q) + xq;
BigInt _encryptInteger(RSAPrivateKey key, BigInt x) {
// The following is equivalent to `(x % key.n).modPow(key.d, key.n)` but is
// much more efficient. It exploits the fact that we have dmp1/dmq1.
var xp = (x % key.p).modPow(key.dmp1, key.p);
final xq = (x % key.q).modPow(key.dmq1, key.q);
while (xp < xq) {
xp += key.p;
}
return ((((xp - xq) * key.coeff) % key.p) * key.q) + xq;
}

static BigInt bytes2BigInt(List<int> bytes) {
var number = BigInt.zero;
for (var i = 0; i < bytes.length; i++) {
number = (number << 8) | BigInt.from(bytes[i]);
}
return number;
@internal
BigInt bytes2BigInt(List<int> bytes) {
var number = BigInt.zero;
for (var i = 0; i < bytes.length; i++) {
number = (number << 8) | BigInt.from(bytes[i]);
}
return number;
}

static Uint8List integer2Bytes(BigInt integer, int intendedLength) {
if (integer < BigInt.one) {
throw ArgumentError('Only positive integers are supported.');
}
final bytes = Uint8List(intendedLength);
for (var i = bytes.length - 1; i >= 0; i--) {
bytes[i] = (integer & _bigIntFF).toInt();
integer >>= 8;
}
return bytes;
@visibleForTesting
Uint8List integer2Bytes(BigInt integer, int intendedLength) {
if (integer < BigInt.one) {
throw ArgumentError('Only positive integers are supported.');
}
final bytes = Uint8List(intendedLength);
for (var i = bytes.length - 1; i >= 0; i--) {
bytes[i] = (integer & _bigIntFF).toInt();
integer >>= 8;
}
return bytes;
}

final _bigIntFF = BigInt.from(0xff);
8 changes: 5 additions & 3 deletions googleapis_auth/lib/src/crypto/rsa_sign.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import 'dart:typed_data';

import 'package:crypto/crypto.dart';
import 'package:meta/meta.dart';

import 'rsa.dart';
import 'rsa.dart' as rsa;

/// Used for signing messages with a private RSA key.
///
/// The implemented algorithm can be seen in
/// RFC 3447, Section 9.2 EMSA-PKCS1-v1_5.
@internal
final class RS256Signer {
// DigestInfo :== SEQUENCE {
// digestAlgorithm AlgorithmIdentifier,
Expand Down Expand Up @@ -53,7 +55,7 @@ final class RS256Signer {
0x20,
];

final RSAPrivateKey _rsaKey;
final rsa.RSAPrivateKey _rsaKey;

RS256Signer(this._rsaKey);

Expand All @@ -74,6 +76,6 @@ final class RS256Signer {
offset += _rsaSha256DigestInfoPrefix.length;
block.setAll(offset, digest);

return RSAAlgorithm.rawSign(_rsaKey, block, modulusLen);
return rsa.rawSign(_rsaKey, block, modulusLen);
}
}
2 changes: 1 addition & 1 deletion googleapis_auth/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: googleapis_auth
version: 2.2.0
version: 2.2.1-wip
description: Obtain Access credentials for Google services using OAuth 2.0
repository: https://github.com/google/googleapis.dart/tree/master/googleapis_auth

Expand Down
29 changes: 8 additions & 21 deletions googleapis_auth/test/crypto/rsa_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

import 'package:googleapis_auth/src/crypto/rsa.dart';
import 'package:googleapis_auth/src/crypto/rsa.dart' as rsa;
import 'package:test/test.dart';

import '../test_utils.dart';
Expand All @@ -14,19 +14,9 @@ final _bigNumber = BigInt.parse('20000000000000000', radix: 16);

void main() {
test('integer2Bytes converts BigInt to byte list', () {
expect(RSAAlgorithm.integer2Bytes(BigInt.one, 1), [1]);
expect(RSAAlgorithm.integer2Bytes(_bigNumber, 9), [
2,
0,
0,
0,
0,
0,
0,
0,
0,
]);
expect(RSAAlgorithm.integer2Bytes(_bigNumber, 12), [
expect(rsa.integer2Bytes(BigInt.one, 1), [1]);
expect(rsa.integer2Bytes(_bigNumber, 9), [2, 0, 0, 0, 0, 0, 0, 0, 0]);
expect(rsa.integer2Bytes(_bigNumber, 12), [
0,
0,
0,
Expand All @@ -40,15 +30,12 @@ void main() {
0,
0,
]);
expect(
() => RSAAlgorithm.integer2Bytes(BigInt.zero, 1),
throwsArgumentError,
);
expect(() => rsa.integer2Bytes(BigInt.zero, 1), throwsArgumentError);
});

test('bytes2BigInt converts byte list to BigInt', () {
expect(RSAAlgorithm.bytes2BigInt([1]), BigInt.one);
expect(RSAAlgorithm.bytes2BigInt([2, 0, 0, 0, 0, 0, 0, 0, 0]), _bigNumber);
expect(rsa.bytes2BigInt([1]), BigInt.one);
expect(rsa.bytes2BigInt([2, 0, 0, 0, 0, 0, 0, 0, 0]), _bigNumber);
});

test('rawSign performs raw RSA signing', () {
Expand All @@ -72,7 +59,7 @@ void main() {
26, 217, 230, 133, 217, 76,
];
expect(
RSAAlgorithm.rawSign(testPrivateKey, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 256),
rsa.rawSign(testPrivateKey, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 256),
encryptedData,
);
});
Expand Down
Loading