Crypto cleanup, doc cleanup, code cleanup (#718)
diff --git a/googleapis_auth/CHANGELOG.md b/googleapis_auth/CHANGELOG.md index 5d9c72c..fa4fad0 100644 --- a/googleapis_auth/CHANGELOG.md +++ b/googleapis_auth/CHANGELOG.md
@@ -1,6 +1,7 @@ ## 2.1.0 -- `AuthClientSigningExtension`: Added `sign()` which accepts an optional `serviceAccountCredentials` argument, and `getServiceAccountEmail()` which accepts an optional `email` argument. +- `AuthClientSigningExtension`: Added `sign()` which accepts an optional + `serviceAccountCredentials` argument, and `getServiceAccountEmail()`. - `ServiceAccountCredentials` - Added parsing for `projectId` and `universeDomain` properties. - Added `sign()` method for RSA-SHA256 signing. @@ -13,7 +14,6 @@ `ImpersonatedAuthClient` class for service account impersonation via IAM Credentials API. - Export `RSAPrivateKey` which is exposed by `ServiceAccountCredentials`. -- Modernized code using pattern matching and switch expressions. - Require `google_cloud: ^0.3.0`. - Require `meta: ^1.15.0`. - Require `sdk: ^3.9.0`.
diff --git a/googleapis_auth/lib/src/crypto/rsa.dart b/googleapis_auth/lib/src/crypto/rsa.dart index 06bab62..80bb389 100644 --- a/googleapis_auth/lib/src/crypto/rsa.dart +++ b/googleapis_auth/lib/src/crypto/rsa.dart
@@ -8,7 +8,7 @@ import 'dart:typed_data'; /// Represents integers obtained while creating a Public/Private key pair. -class RSAPrivateKey { +final class RSAPrivateKey { /// First prime number. final BigInt p; @@ -70,34 +70,16 @@ } static BigInt _encryptInteger(RSAPrivateKey key, BigInt x) { - // The following is equivalent to `_modPow(x, key.d, key.n) but is much - // more efficient. It exploits the fact that we have dmp1/dmq1. - var xp = _modPow(x % key.p, key.dmp1, key.p); - final xq = _modPow(x % key.q, key.dmq1, key.q); + // 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 _modPow(BigInt b, BigInt e, BigInt m) { - if (e < BigInt.one) { - return BigInt.one; - } - if (b < BigInt.zero || b > m) { - b = b % m; - } - var r = BigInt.one; - while (e > BigInt.zero) { - if ((e & BigInt.one) > BigInt.zero) { - r = (r * b) % m; - } - e >>= 1; - b = (b * b) % m; - } - return r; - } - static BigInt bytes2BigInt(List<int> bytes) { var number = BigInt.zero; for (var i = 0; i < bytes.length; i++) {
diff --git a/googleapis_auth/lib/src/crypto/rsa_sign.dart b/googleapis_auth/lib/src/crypto/rsa_sign.dart index 3c945b6..4fb4c9b 100644 --- a/googleapis_auth/lib/src/crypto/rsa_sign.dart +++ b/googleapis_auth/lib/src/crypto/rsa_sign.dart
@@ -6,18 +6,34 @@ import 'package:crypto/crypto.dart'; -import 'asn1.dart'; import 'rsa.dart'; /// 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. -class RS256Signer { - // NIST sha-256 OID (2 16 840 1 101 3 4 2 1) +final class RS256Signer { + // DigestInfo :== SEQUENCE { + // digestAlgorithm AlgorithmIdentifier, + // digest OCTET STRING + // } + // + // Where AlgorithmIdentifier is for the NIST sha-256 OID + // (2 16 840 1 101 3 4 2 1) // See a reference for the encoding here: // http://msdn.microsoft.com/en-us/library/bb540809%28v=vs.85%29.aspx - static const _rsaSha256AlgorithmIdentifier = [ + // ASN1: + // SEQUENCE (length: 0x1f + 2 for sequence + length) [0x30, 0x31] + // SEQUENCE (length: 0x0d + 2 for sequence + length) [0x30, 0x0d] + // OID 2.16.840.1.101.3.4.2.1 + // [0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01] + // NULL [0x05, 0x00] + // OCTET STRING (length: 0x20 + 2 for octet string + length) [0x04, 0x20] + static const _rsaSha256DigestInfoPrefix = [ + 0x30, + 0x31, + 0x30, + 0x0d, 0x06, 0x09, 0x60, @@ -29,6 +45,10 @@ 0x04, 0x02, 0x01, + 0x05, + 0x00, + 0x04, + 0x20, ]; final RSAPrivateKey _rsaKey; @@ -36,45 +56,22 @@ RS256Signer(this._rsaKey); Uint8List sign(List<int> bytes) { - final digest = _digestInfo(sha256.convert(bytes).bytes); + final digest = sha256.convert(bytes).bytes; final modulusLen = (_rsaKey.bitLength + 7) ~/ 8; final block = Uint8List(modulusLen); - final padLength = block.length - digest.length - 3; + final padLength = + block.length - _rsaSha256DigestInfoPrefix.length - digest.length - 3; block[0] = 0x00; block[1] = 0x01; block.fillRange(2, 2 + padLength, 0xFF); block[2 + padLength] = 0x00; - block.setRange(2 + padLength + 1, block.length, digest); - return RSAAlgorithm.rawSign(_rsaKey, block, modulusLen); - } - static Uint8List _digestInfo(List<int> hash) { - // DigestInfo :== SEQUENCE { - // digestAlgorithm AlgorithmIdentifier, - // digest OCTET STRING - // } - var offset = 0; - final digestInfo = Uint8List( - 2 + 2 + _rsaSha256AlgorithmIdentifier.length + 2 + 2 + hash.length, - ); - { - // DigestInfo - digestInfo[offset++] = ASN1Parser.sequenceTag; - digestInfo[offset++] = digestInfo.length - 2; - { - // AlgorithmIdentifier. - digestInfo[offset++] = ASN1Parser.sequenceTag; - digestInfo[offset++] = _rsaSha256AlgorithmIdentifier.length + 2; - digestInfo.setAll(offset, _rsaSha256AlgorithmIdentifier); - offset += _rsaSha256AlgorithmIdentifier.length; - digestInfo[offset++] = ASN1Parser.nullTag; - digestInfo[offset++] = 0; - } - digestInfo[offset++] = ASN1Parser.octetStringTag; - digestInfo[offset++] = hash.length; - digestInfo.setAll(offset, hash); - } - return digestInfo; + var offset = 2 + padLength + 1; + block.setAll(offset, _rsaSha256DigestInfoPrefix); + offset += _rsaSha256DigestInfoPrefix.length; + block.setAll(offset, digest); + + return RSAAlgorithm.rawSign(_rsaKey, block, modulusLen); } }
diff --git a/googleapis_auth/lib/src/service_account_credentials.dart b/googleapis_auth/lib/src/service_account_credentials.dart index a403442..a065467 100644 --- a/googleapis_auth/lib/src/service_account_credentials.dart +++ b/googleapis_auth/lib/src/service_account_credentials.dart
@@ -59,41 +59,36 @@ if (json is String) { json = jsonDecode(json); } - if (json is! Map) { - throw ArgumentError('json must be a Map or a String encoding a Map.'); - } - // TODO: consider using expressions here - final identifier = json['client_id'] as String?; - final privateKey = json['private_key'] as String?; - final email = json['client_email'] as String?; - final type = json['type']; - final projectId = json['project_id'] as String?; - final universeDomain = - json['universe_domain'] as String? ?? defaultUniverseDomain; - - if (type != 'service_account') { - throw ArgumentError( - 'The given credentials are not of type ' - 'service_account (was: $type).', - ); - } - - if (identifier == null || privateKey == null || email == null) { - throw ArgumentError( + return switch (json) { + final Map map && + { + 'type': 'service_account', + 'client_id': final String identifier, + 'private_key': final String privateKey, + 'client_email': final String email, + } => + ServiceAccountCredentials( + email, + ClientId(identifier), + privateKey, + impersonatedUser: impersonatedUser, + projectId: map['project_id'] as String?, + universeDomain: + map['universe_domain'] as String? ?? defaultUniverseDomain, + ), + final Map map when map['type'] != 'service_account' => + throw ArgumentError( + 'The given credentials are not of type ' + 'service_account (was: ${map['type']}).', + ), + Map _ => throw ArgumentError( 'The given credentials do not contain all the ' 'fields: client_id, private_key and client_email.', - ); - } - - final clientId = ClientId(identifier); - return ServiceAccountCredentials( - email, - clientId, - privateKey, - impersonatedUser: impersonatedUser, - projectId: projectId, - universeDomain: universeDomain, - ); + ), + _ => throw ArgumentError( + 'json must be a Map or a String encoding a Map.', + ), + }; } /// Creates a new [ServiceAccountCredentials].
diff --git a/googleapis_auth/test/crypto/rsa_sign_test.dart b/googleapis_auth/test/crypto/rsa_sign_test.dart index 9e0d135..f2d61c4 100644 --- a/googleapis_auth/test/crypto/rsa_sign_test.dart +++ b/googleapis_auth/test/crypto/rsa_sign_test.dart
@@ -79,4 +79,11 @@ 184, 21, 130, 61, 138, 27, 226, 70, 119, 195, 223, 180, 121, ]); }); + + test('different messages generate different signatures', () { + final sig1 = signer.sign(ascii.encode('test message A')); + final sig2 = signer.sign(ascii.encode('test message B')); + + expect(sig1, isNot(equals(sig2))); + }); }