blob: b92a93d14b93cc2066aa06b28173a177728c62fc [file] [edit]
// Copyright 2021 Google LLC
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// ignore_for_file: lines_longer_than_80_chars
import 'dart:convert';
import 'package:googleapis_auth/src/crypto/pem.dart';
import 'package:test/test.dart';
import '../test_utils.dart';
void main() {
test('throws ArgumentError for empty string', () {
expect(() => keyFromString(''), throwsFormatException);
});
test('keyFromString parses valid RSA private key', () {
final key = keyFromString(testPrivateKeyString);
expect(
key.p,
BigInt.parse(
'170185878019789847607218833833962851295383479739128068911675681859184825725303329240997154492057125840628991571181411414164882361723231273391547091096391845233984484218520948165420605211532206383859989286454330226302062891556391372178426684136261758077913279309249468965000813860343415338472623037185763380093',
),
);
expect(
key.q,
BigInt.parse(
'136634937867625346722869734066327766542560453705266659651284573193680854438532412351608161985232086174999341126075829838477923122149398705411098928405144549034231120055200290950893136823181693383585861140730929930114638604738429489364496581584222788741142343940831827356789459450282075298628271623617861448279',
),
);
expect(
key.n,
BigInt.parse(
'23253336869181252005308127869627478511861722018560725538542603352356752658510633204810959681459083455055115233727694253121121138828979138624495569601457246561359553177524606534054439784597124760679930421448728375265700767584567585959695707287695356045087640902894887625471020788794811661755081070077086649519865067918501869783817745592796089436450623267438942174934673417424553992577792939276705103879103955476795626469391055763713456179432199172562526422301070938382514265029982800538033050279129668807032677927531973249309321914500317007151921938466293582589451642241740444272968677617027011566610435323463337709947',
),
);
expect(
key.d,
BigInt.parse(
'21186554940454261253047269959735660724480631477978821785517431853394668885438560354085051566279884512080977781045029208574826211785037495240030508751426142586201712610225510861978099522679761260199887167944008250970681053969661407950094604171122649803382413195502685962008111346880629170494825836648656453852203519401121722270587408277317819537925146228717860401265662699719826243356610955461998054615517371279631680512102389979478015385709644867750888484550190071229275090881149432467365050794063725847869274512118390103343213000471284707060203072264487986083004823016463235156640750689592865369834958756866148520449',
),
);
expect(key.e, BigInt.from(65537));
expect(
key.dmp1,
BigInt.parse(
'8112374428701702609593842209702915108210293280208677346843383586799722226617751812699316578927727255231777006398991855865405686833748485558923861522271817820635175987589597358267451526325993144989526626651865780047418167954318425419006133348210655541684866328365584952723843668457708310075048817739114161457',
),
);
expect(
key.dmq1,
BigInt.parse(
'69064888333930830841944331910451194321610695483381427808232052980561601308959263072336597373770287299070802348040252301131546443496698520136006747353055884093824470361301555431744464097251017848208627523520965497274938325818544542688522182250340240209771921627903870254182590341478772425006618460954711021211',
),
);
expect(
key.coeff,
BigInt.parse(
'16726959063327324857338379758571748557044292252371297447561270320393087678399207080059961434627453370656491757664831584315003981946034135341817305303511530360890203726058358401094205679273808207987503167082629712433452873772120961093571912870024590300080209978748890272607981079166485164486378666155431958545',
),
);
});
test('throws ArgumentError for invalid OID', () {
// Construct a PEM with invalid OID in Encapsulated format.
// Encapsulated := seq[int/version=0, seq[obj-id/wrong, null-obj], octet-string/PrivateKey]
// Wrong OID: 1.2.840.113549.1.1.2 (MD2 with RSAEncryption) instead of 1.1.1
// 1.2.840.113549.1.1.2 -> 2a 86 48 86 f7 0d 01 01 02
final wrongOidBytes = [
0x30, 0x0D, // Sequence (OID + NULL)
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x02, // OID
0x05, 0x00, // Null
];
// We need to wrap the private key in OctetString.
// Existing key bytes:
final keyBytes = base64Decode(
testPrivateKeyString
.replaceAll('-----BEGIN RSA PRIVATE KEY-----', '')
.replaceAll('-----END RSA PRIVATE KEY-----', '')
.replaceAll('\n', ''),
);
// Wrap in OctetString
// OctetString tag 0x04. Length is long form.
final octetHeader = <int>[0x04];
if (keyBytes.length < 128) {
octetHeader.add(keyBytes.length);
} else {
final lenBytes = <int>[];
var l = keyBytes.length;
while (l > 0) {
lenBytes.insert(0, l & 0xFF);
l >>= 8;
}
octetHeader.add(0x80 | lenBytes.length);
octetHeader.addAll(lenBytes);
}
// Final Sequence: Version 0, WrongAlgo, OctetString(Key)
// Version 0: 02 01 00
final fullSeqContent = <int>[
0x02,
0x01,
0x00,
...wrongOidBytes,
...octetHeader,
...keyBytes,
];
final fullSeqHeader = <int>[0x30]; // Sequence
// Length
final fullLen = fullSeqContent.length;
if (fullLen < 128) {
fullSeqHeader.add(fullLen);
} else {
final lenBytes = <int>[];
var l = fullLen;
while (l > 0) {
lenBytes.insert(0, l & 0xFF);
l >>= 8;
}
fullSeqHeader.add(0x80 | lenBytes.length);
fullSeqHeader.addAll(lenBytes);
}
final fullBytes = [...fullSeqHeader, ...fullSeqContent];
final pem =
'-----BEGIN RSA PRIVATE KEY-----\n${base64Encode(fullBytes)}\n-----END RSA PRIVATE KEY-----';
expect(
() => keyFromString(pem),
_throwFormatExceptionWithMsg('Unexpected Algorithm Identifier OID'),
);
});
test('throws ArgumentError for key smaller than 1024 bits', () {
// 512 bit key (mocked, simple ints that parse but have small n).
// Seq(0, n, e, d, p, q, dmp1, dmq1, coeff)
// n approx 2^511.
final n = BigInt.two.pow(511);
final e = BigInt.from(65537);
// d, p, q etc can be 0 or 1 for parsing, avoiding complex math.
// The bitLength check is on n.
final d = BigInt.one;
final p = BigInt.one;
final q = BigInt.one;
final dmp1 = BigInt.one;
final dmq1 = BigInt.one;
final coeff = BigInt.one;
List<int> encodeBigInt(BigInt i) {
if (i == BigInt.zero) return [0];
final bytes = <int>[];
var temp = i;
while (temp > BigInt.zero) {
bytes.insert(0, (temp & BigInt.from(0xFF)).toInt());
temp >>= 8;
}
if ((bytes[0] & 0x80) != 0) {
bytes.insert(0, 0);
}
return bytes;
}
List<int> ans1Int(BigInt i) {
final b = encodeBigInt(i);
return [
0x02,
b.length,
...b,
]; // Assuming length < 128 (512 bits is ~64 bytes)
}
final seqContent = <int>[
...ans1Int(BigInt.zero), // ver
...ans1Int(n),
...ans1Int(e),
...ans1Int(d),
...ans1Int(p),
...ans1Int(q),
...ans1Int(dmp1),
...ans1Int(dmq1),
...ans1Int(coeff),
];
final seqLen = seqContent.length;
final fullBytes = [0x30];
if (seqLen < 128) {
fullBytes.add(seqLen);
} else {
fullBytes.add(0x81); // 1 byte length
fullBytes.add(seqLen);
}
fullBytes.addAll(seqContent);
final pem =
'''
-----BEGIN RSA PRIVATE KEY-----
${base64Encode(fullBytes)}
-----END RSA PRIVATE KEY-----''';
expect(
() => keyFromString(pem),
_throwFormatExceptionWithMsg('Only 1024 or more bits are supported'),
);
});
}
Matcher _throwFormatExceptionWithMsg(String msg) => throwsA(
isFormatException.having((e) => e.message, 'message', contains(msg)),
);