mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #1853 from mpcomplete/tests
Some initial unit tests for package signing
This commit is contained in:
commit
4b7d00a19f
@ -4,7 +4,6 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@ -44,7 +43,7 @@ final CipherParameters _params = _initParams();
|
||||
Uint8List serializeManifest(Map manifestDescriptor, ECPublicKey publicKey, Uint8List zipBytes) {
|
||||
if (manifestDescriptor == null)
|
||||
return null;
|
||||
final List<String> kSavedKeys = [
|
||||
final List<String> kSavedKeys = <String>[
|
||||
'name',
|
||||
'version',
|
||||
'update-url'
|
||||
@ -66,9 +65,9 @@ Uint8List serializeManifest(Map manifestDescriptor, ECPublicKey publicKey, Uint8
|
||||
}
|
||||
|
||||
// Returns the ASN.1 encoded signature of the input manifestBytes.
|
||||
List<int> signManifest(Uint8List manifestBytes, ECPrivateKey privateKey) {
|
||||
Uint8List signManifest(Uint8List manifestBytes, ECPrivateKey privateKey) {
|
||||
if (manifestBytes == null || privateKey == null)
|
||||
return [];
|
||||
return new Uint8List(0);
|
||||
Signer signer = new Signer(_params.signerAlgorithm);
|
||||
PrivateKeyParameter params = new PrivateKeyParameter(privateKey);
|
||||
signer.init(true, new ParametersWithRandom(params, _params.random));
|
||||
@ -121,26 +120,22 @@ ECPrivateKey _asn1ParsePrivateKey(ECDomainParameters ecDomain, Uint8List private
|
||||
|
||||
// Parses a DER-encoded ASN.1 ECDSA signature block.
|
||||
ECSignature _asn1ParseSignature(Uint8List signature) {
|
||||
ASN1Parser parser = new ASN1Parser(signature);
|
||||
ASN1Object object = parser.nextObject();
|
||||
if (object is! ASN1Sequence)
|
||||
try {
|
||||
ASN1Parser parser = new ASN1Parser(signature);
|
||||
ASN1Object object = parser.nextObject();
|
||||
if (object is! ASN1Sequence)
|
||||
return null;
|
||||
ASN1Sequence sequence = object;
|
||||
if (!(sequence.elements.length == 2 &&
|
||||
sequence.elements[0] is ASN1Integer &&
|
||||
sequence.elements[1] is ASN1Integer))
|
||||
return null;
|
||||
ASN1Integer r = sequence.elements[0];
|
||||
ASN1Integer s = sequence.elements[1];
|
||||
return new ECSignature(r.valueAsPositiveBigInteger, s.valueAsPositiveBigInteger);
|
||||
} on ASN1Exception {
|
||||
return null;
|
||||
ASN1Sequence sequence = object;
|
||||
if (!(sequence.elements.length == 2 &&
|
||||
sequence.elements[0] is ASN1Integer &&
|
||||
sequence.elements[1] is ASN1Integer))
|
||||
return null;
|
||||
ASN1Integer r = sequence.elements[0];
|
||||
ASN1Integer s = sequence.elements[1];
|
||||
return new ECSignature(r.valueAsPositiveBigInteger, s.valueAsPositiveBigInteger);
|
||||
}
|
||||
|
||||
ECPrivateKey _readPrivateKeySync(String privateKeyPath) {
|
||||
File file = new File(privateKeyPath);
|
||||
if (!file.existsSync())
|
||||
return null;
|
||||
List<int> bytes = file.readAsBytesSync();
|
||||
return _asn1ParsePrivateKey(_params.domain, new Uint8List.fromList(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
ECPublicKey _publicKeyFromPrivateKey(ECPrivateKey privateKey) {
|
||||
@ -154,8 +149,16 @@ class KeyPair {
|
||||
ECPublicKey publicKey;
|
||||
ECPrivateKey privateKey;
|
||||
|
||||
static KeyPair readFromPrivateKeySync(String path) {
|
||||
ECPrivateKey privateKey = _readPrivateKeySync(path);
|
||||
static KeyPair readFromPrivateKeySync(String privateKeyPath) {
|
||||
File file = new File(privateKeyPath);
|
||||
if (!file.existsSync())
|
||||
return null;
|
||||
return fromPrivateKeyBytes(file.readAsBytesSync());
|
||||
}
|
||||
|
||||
static KeyPair fromPrivateKeyBytes(List<int> privateKeyBytes) {
|
||||
ECPrivateKey privateKey = _asn1ParsePrivateKey(
|
||||
_params.domain, new Uint8List.fromList(privateKeyBytes));
|
||||
if (privateKey == null)
|
||||
return null;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
name: flx
|
||||
version: 0.0.1
|
||||
version: 0.0.2
|
||||
author: Flutter Authors <flutter-dev@googlegroups.com>
|
||||
description: Library for dealing with Flutter bundle (.flx) files
|
||||
homepage: http://flutter.io
|
||||
|
||||
@ -11,6 +11,7 @@ dependencies:
|
||||
flutter_rendering_examples: any
|
||||
flutter_sprites: any
|
||||
playfair: any
|
||||
flx: any
|
||||
dependency_overrides:
|
||||
material_design_icons:
|
||||
path: ../material_design_icons
|
||||
@ -20,5 +21,7 @@ dependency_overrides:
|
||||
path: ../../../examples/rendering
|
||||
flutter_sprites:
|
||||
path: ../../../skysprites
|
||||
flx:
|
||||
path: ../flx
|
||||
environment:
|
||||
sdk: '>=1.8.0 <2.0.0'
|
||||
|
||||
@ -4,8 +4,11 @@ dependencies:
|
||||
sky_tools: any
|
||||
test: any
|
||||
quiver: any
|
||||
flx: ^0.0.2
|
||||
dependency_overrides:
|
||||
material_design_icons:
|
||||
path: ../packages/material_design_icons
|
||||
flutter:
|
||||
path: ../packages/sky
|
||||
flx:
|
||||
path: ../packages/flx
|
||||
|
||||
100
sky/unit/test/flx/signing_test.dart
Normal file
100
sky/unit/test/flx/signing_test.dart
Normal file
@ -0,0 +1,100 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bignum/bignum.dart';
|
||||
import 'package:flx/signing.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
// The following constant was generated via the openssl shell commands:
|
||||
// openssl ecparam -genkey -name prime256v1 -out privatekey.pem
|
||||
// openssl ec -in privatekey.pem -outform DER | base64
|
||||
const String kPrivateKeyBase64 = 'MHcCAQEEIG4Xt+MgsdP/o89kAHz7EVVLKkN+DUfpaBtZfMyFGbUgoAoGCCqGSM49AwEHoUQDQgAElPtbBVPPqKHYXYAgHaxB2hL6sXeFc99YLijTAuAPe2Nbhywan+v4k+nFm0TJJW/mkV+nH+fyBZ98t4UcFCqkOg==';
|
||||
final List<int> kPrivateKeyDER = BASE64.decode(kPrivateKeyBase64);
|
||||
|
||||
// Unpacked values of the above private key.
|
||||
const int kPrivateKeyD = 0x6e17b7e320b1d3ffa3cf64007cfb11554b2a437e0d47e9681b597ccc8519b520;
|
||||
const int kPublicKeyQx = 0x94fb5b0553cfa8a1d85d80201dac41da12fab1778573df582e28d302e00f7b63;
|
||||
const int kPublicKeyQy = 0x5b872c1a9febf893e9c59b44c9256fe6915fa71fe7f2059f7cb7851c142aa43a;
|
||||
|
||||
// Test manifest.
|
||||
final Map<String, dynamic> kManifest = <String, dynamic>{
|
||||
'name': 'test app',
|
||||
'version': '1.0.0'
|
||||
};
|
||||
|
||||
// Simple test byte pattern (flat and in chunked form) and its SHA-256 hash.
|
||||
final Uint8List kTestBytes = new Uint8List.fromList(<int>[1, 2, 3]);
|
||||
final List<Uint8List> kTestBytesList = <Uint8List>[
|
||||
new Uint8List.fromList(<int>[1, 2]), new Uint8List.fromList(<int>[3])];
|
||||
final int kTestHash = 0x039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
|
||||
|
||||
test('can read openssl key pair', () {
|
||||
KeyPair keyPair = KeyPair.fromPrivateKeyBytes(kPrivateKeyDER);
|
||||
expect(keyPair != null, equals(true));
|
||||
expect(keyPair.privateKey.d.intValue(), equals(kPrivateKeyD));
|
||||
expect(keyPair.publicKey.Q.x.toBigInteger().intValue(), equals(kPublicKeyQx));
|
||||
expect(keyPair.publicKey.Q.y.toBigInteger().intValue(), equals(kPublicKeyQy));
|
||||
});
|
||||
|
||||
test('serializeManifest adds key and content-hash', () {
|
||||
KeyPair keyPair = KeyPair.fromPrivateKeyBytes(kPrivateKeyDER);
|
||||
Uint8List manifestBytes = serializeManifest(kManifest, keyPair.publicKey, kTestBytes);
|
||||
Map<String, dynamic> decodedManifest = JSON.decode(UTF8.decode(manifestBytes));
|
||||
String expectedKey = BASE64.encode(keyPair.publicKey.Q.getEncoded());
|
||||
expect(decodedManifest != null, equals(true));
|
||||
expect(decodedManifest['name'], equals(kManifest['name']));
|
||||
expect(decodedManifest['version'], equals(kManifest['version']));
|
||||
expect(decodedManifest['key'], equals(expectedKey));
|
||||
expect(decodedManifest['content-hash'], equals(kTestHash));
|
||||
});
|
||||
|
||||
test('signManifest and verifyManifestSignature work', () {
|
||||
KeyPair keyPair = KeyPair.fromPrivateKeyBytes(kPrivateKeyDER);
|
||||
Map<String, dynamic> manifest = JSON.decode(UTF8.decode(
|
||||
serializeManifest(kManifest, keyPair.publicKey, kTestBytes)));
|
||||
Uint8List signatureBytes = signManifest(kTestBytes, keyPair.privateKey);
|
||||
|
||||
bool verifies = verifyManifestSignature(manifest, kTestBytes, signatureBytes);
|
||||
expect(verifies, equals(true));
|
||||
|
||||
// Ensure it fails with invalid signature or content.
|
||||
Uint8List badBytes = new Uint8List.fromList(<int>[42]);
|
||||
verifies = verifyManifestSignature(manifest, kTestBytes, badBytes);
|
||||
expect(verifies, equals(false));
|
||||
verifies = verifyManifestSignature(manifest, badBytes, signatureBytes);
|
||||
expect(verifies, equals(false));
|
||||
});
|
||||
|
||||
test('verifyContentHash works', () {
|
||||
new FakeAsync().run((FakeAsync async) {
|
||||
bool verifies;
|
||||
Stream contentStream = new Stream.fromIterable(kTestBytesList);
|
||||
verifyContentHash(new BigInteger(kTestHash), contentStream).then((bool rv) {
|
||||
verifies = rv;
|
||||
});
|
||||
async.elapse(Duration.ZERO);
|
||||
expect(verifies, equals(true));
|
||||
|
||||
// Ensure it fails with invalid hash or content.
|
||||
verifies = null;
|
||||
contentStream = new Stream.fromIterable(kTestBytesList);
|
||||
verifyContentHash(new BigInteger(0xdeadbeef), contentStream).then((bool rv) {
|
||||
verifies = rv;
|
||||
});
|
||||
async.elapse(Duration.ZERO);
|
||||
expect(verifies, equals(false));
|
||||
|
||||
verifies = null;
|
||||
Stream badContentStream =
|
||||
new Stream.fromIterable([new Uint8List.fromList(<int>[42])]);
|
||||
verifyContentHash(new BigInteger(kTestHash), badContentStream).then((bool rv) {
|
||||
verifies = rv;
|
||||
});
|
||||
async.elapse(Duration.ZERO);
|
||||
expect(verifies, equals(false));
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user