Skip to content

Commit e837076

Browse files
authored
Crypto cleanup, doc cleanup, code cleanup (#718)
1 parent 3ebe7d1 commit e837076

File tree

5 files changed

+75
-94
lines changed

5 files changed

+75
-94
lines changed

googleapis_auth/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 2.1.0
22

3-
- `AuthClientSigningExtension`: Added `sign()` which accepts an optional `serviceAccountCredentials` argument, and `getServiceAccountEmail()` which accepts an optional `email` argument.
3+
- `AuthClientSigningExtension`: Added `sign()` which accepts an optional
4+
`serviceAccountCredentials` argument, and `getServiceAccountEmail()`.
45
- `ServiceAccountCredentials`
56
- Added parsing for `projectId` and `universeDomain` properties.
67
- Added `sign()` method for RSA-SHA256 signing.
@@ -13,7 +14,6 @@
1314
`ImpersonatedAuthClient` class for service account impersonation via IAM
1415
Credentials API.
1516
- Export `RSAPrivateKey` which is exposed by `ServiceAccountCredentials`.
16-
- Modernized code using pattern matching and switch expressions.
1717
- Require `google_cloud: ^0.3.0`.
1818
- Require `meta: ^1.15.0`.
1919
- Require `sdk: ^3.9.0`.

googleapis_auth/lib/src/crypto/rsa.dart

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import 'dart:typed_data';
99

1010
/// Represents integers obtained while creating a Public/Private key pair.
11-
class RSAPrivateKey {
11+
final class RSAPrivateKey {
1212
/// First prime number.
1313
final BigInt p;
1414

@@ -70,34 +70,16 @@ abstract final class RSAAlgorithm {
7070
}
7171

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

83-
static BigInt _modPow(BigInt b, BigInt e, BigInt m) {
84-
if (e < BigInt.one) {
85-
return BigInt.one;
86-
}
87-
if (b < BigInt.zero || b > m) {
88-
b = b % m;
89-
}
90-
var r = BigInt.one;
91-
while (e > BigInt.zero) {
92-
if ((e & BigInt.one) > BigInt.zero) {
93-
r = (r * b) % m;
94-
}
95-
e >>= 1;
96-
b = (b * b) % m;
97-
}
98-
return r;
99-
}
100-
10183
static BigInt bytes2BigInt(List<int> bytes) {
10284
var number = BigInt.zero;
10385
for (var i = 0; i < bytes.length; i++) {

googleapis_auth/lib/src/crypto/rsa_sign.dart

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,34 @@ import 'dart:typed_data';
66

77
import 'package:crypto/crypto.dart';
88

9-
import 'asn1.dart';
109
import 'rsa.dart';
1110

1211
/// Used for signing messages with a private RSA key.
1312
///
1413
/// The implemented algorithm can be seen in
1514
/// RFC 3447, Section 9.2 EMSA-PKCS1-v1_5.
16-
class RS256Signer {
17-
// NIST sha-256 OID (2 16 840 1 101 3 4 2 1)
15+
final class RS256Signer {
16+
// DigestInfo :== SEQUENCE {
17+
// digestAlgorithm AlgorithmIdentifier,
18+
// digest OCTET STRING
19+
// }
20+
//
21+
// Where AlgorithmIdentifier is for the NIST sha-256 OID
22+
// (2 16 840 1 101 3 4 2 1)
1823
// See a reference for the encoding here:
1924
// http://msdn.microsoft.com/en-us/library/bb540809%28v=vs.85%29.aspx
20-
static const _rsaSha256AlgorithmIdentifier = [
25+
// ASN1:
26+
// SEQUENCE (length: 0x1f + 2 for sequence + length) [0x30, 0x31]
27+
// SEQUENCE (length: 0x0d + 2 for sequence + length) [0x30, 0x0d]
28+
// OID 2.16.840.1.101.3.4.2.1
29+
// [0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
30+
// NULL [0x05, 0x00]
31+
// OCTET STRING (length: 0x20 + 2 for octet string + length) [0x04, 0x20]
32+
static const _rsaSha256DigestInfoPrefix = [
33+
0x30,
34+
0x31,
35+
0x30,
36+
0x0d,
2137
0x06,
2238
0x09,
2339
0x60,
@@ -29,52 +45,33 @@ class RS256Signer {
2945
0x04,
3046
0x02,
3147
0x01,
48+
0x05,
49+
0x00,
50+
0x04,
51+
0x20,
3252
];
3353

3454
final RSAPrivateKey _rsaKey;
3555

3656
RS256Signer(this._rsaKey);
3757

3858
Uint8List sign(List<int> bytes) {
39-
final digest = _digestInfo(sha256.convert(bytes).bytes);
59+
final digest = sha256.convert(bytes).bytes;
4060
final modulusLen = (_rsaKey.bitLength + 7) ~/ 8;
4161

4262
final block = Uint8List(modulusLen);
43-
final padLength = block.length - digest.length - 3;
63+
final padLength =
64+
block.length - _rsaSha256DigestInfoPrefix.length - digest.length - 3;
4465
block[0] = 0x00;
4566
block[1] = 0x01;
4667
block.fillRange(2, 2 + padLength, 0xFF);
4768
block[2 + padLength] = 0x00;
48-
block.setRange(2 + padLength + 1, block.length, digest);
49-
return RSAAlgorithm.rawSign(_rsaKey, block, modulusLen);
50-
}
5169

52-
static Uint8List _digestInfo(List<int> hash) {
53-
// DigestInfo :== SEQUENCE {
54-
// digestAlgorithm AlgorithmIdentifier,
55-
// digest OCTET STRING
56-
// }
57-
var offset = 0;
58-
final digestInfo = Uint8List(
59-
2 + 2 + _rsaSha256AlgorithmIdentifier.length + 2 + 2 + hash.length,
60-
);
61-
{
62-
// DigestInfo
63-
digestInfo[offset++] = ASN1Parser.sequenceTag;
64-
digestInfo[offset++] = digestInfo.length - 2;
65-
{
66-
// AlgorithmIdentifier.
67-
digestInfo[offset++] = ASN1Parser.sequenceTag;
68-
digestInfo[offset++] = _rsaSha256AlgorithmIdentifier.length + 2;
69-
digestInfo.setAll(offset, _rsaSha256AlgorithmIdentifier);
70-
offset += _rsaSha256AlgorithmIdentifier.length;
71-
digestInfo[offset++] = ASN1Parser.nullTag;
72-
digestInfo[offset++] = 0;
73-
}
74-
digestInfo[offset++] = ASN1Parser.octetStringTag;
75-
digestInfo[offset++] = hash.length;
76-
digestInfo.setAll(offset, hash);
77-
}
78-
return digestInfo;
70+
var offset = 2 + padLength + 1;
71+
block.setAll(offset, _rsaSha256DigestInfoPrefix);
72+
offset += _rsaSha256DigestInfoPrefix.length;
73+
block.setAll(offset, digest);
74+
75+
return RSAAlgorithm.rawSign(_rsaKey, block, modulusLen);
7976
}
8077
}

googleapis_auth/lib/src/service_account_credentials.dart

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -59,41 +59,36 @@ class ServiceAccountCredentials {
5959
if (json is String) {
6060
json = jsonDecode(json);
6161
}
62-
if (json is! Map) {
63-
throw ArgumentError('json must be a Map or a String encoding a Map.');
64-
}
65-
// TODO: consider using expressions here
66-
final identifier = json['client_id'] as String?;
67-
final privateKey = json['private_key'] as String?;
68-
final email = json['client_email'] as String?;
69-
final type = json['type'];
70-
final projectId = json['project_id'] as String?;
71-
final universeDomain =
72-
json['universe_domain'] as String? ?? defaultUniverseDomain;
73-
74-
if (type != 'service_account') {
75-
throw ArgumentError(
76-
'The given credentials are not of type '
77-
'service_account (was: $type).',
78-
);
79-
}
80-
81-
if (identifier == null || privateKey == null || email == null) {
82-
throw ArgumentError(
62+
return switch (json) {
63+
final Map map &&
64+
{
65+
'type': 'service_account',
66+
'client_id': final String identifier,
67+
'private_key': final String privateKey,
68+
'client_email': final String email,
69+
} =>
70+
ServiceAccountCredentials(
71+
email,
72+
ClientId(identifier),
73+
privateKey,
74+
impersonatedUser: impersonatedUser,
75+
projectId: map['project_id'] as String?,
76+
universeDomain:
77+
map['universe_domain'] as String? ?? defaultUniverseDomain,
78+
),
79+
final Map map when map['type'] != 'service_account' =>
80+
throw ArgumentError(
81+
'The given credentials are not of type '
82+
'service_account (was: ${map['type']}).',
83+
),
84+
Map _ => throw ArgumentError(
8385
'The given credentials do not contain all the '
8486
'fields: client_id, private_key and client_email.',
85-
);
86-
}
87-
88-
final clientId = ClientId(identifier);
89-
return ServiceAccountCredentials(
90-
email,
91-
clientId,
92-
privateKey,
93-
impersonatedUser: impersonatedUser,
94-
projectId: projectId,
95-
universeDomain: universeDomain,
96-
);
87+
),
88+
_ => throw ArgumentError(
89+
'json must be a Map or a String encoding a Map.',
90+
),
91+
};
9792
}
9893

9994
/// Creates a new [ServiceAccountCredentials].

googleapis_auth/test/crypto/rsa_sign_test.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,11 @@ void main() {
7979
184, 21, 130, 61, 138, 27, 226, 70, 119, 195, 223, 180, 121,
8080
]);
8181
});
82+
83+
test('different messages generate different signatures', () {
84+
final sig1 = signer.sign(ascii.encode('test message A'));
85+
final sig2 = signer.sign(ascii.encode('test message B'));
86+
87+
expect(sig1, isNot(equals(sig2)));
88+
});
8289
}

0 commit comments

Comments
 (0)