From e674deca9089bcf087fba9ee1644d6ca02157f6e Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 3 Nov 2015 14:50:47 -0500 Subject: [PATCH] Add tests for flx Bundle. Also cleaned up the flx code a bit. Replaced custom KeyPair class with cipher's AsymmetricKeyPair. --- .../flutter/sky/packages/flx/lib/bundle.dart | 32 +++++++------- .../flutter/sky/packages/flx/lib/signing.dart | 42 +++++++++++-------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/engine/src/flutter/sky/packages/flx/lib/bundle.dart b/engine/src/flutter/sky/packages/flx/lib/bundle.dart index 52485c8efa1..f5afe4492d0 100644 --- a/engine/src/flutter/sky/packages/flx/lib/bundle.dart +++ b/engine/src/flutter/sky/packages/flx/lib/bundle.dart @@ -18,6 +18,8 @@ const String kBundleMagic = '#!mojo mojo:sky_viewer\n'; // more flexbile about what we accept. const String kBundleMagicPrefix = '#!mojo '; +typedef Stream> StreamOpener(); + Future> _readBytesWithLength(RandomAccessFile file) async { ByteData buffer = new ByteData(4); await file.readInto(buffer.buffer.asUint8List()); @@ -39,13 +41,13 @@ Future _readLine(RandomAccessFile file) async { } // Writes a 32-bit length followed by the content of [bytes]. -void _writeBytesWithLengthSync(File outputFile, List bytes) { +void _writeBytesWithLengthSync(RandomAccessFile outputFile, List bytes) { if (bytes == null) bytes = new Uint8List(0); assert(bytes.length < 0xffffffff); ByteData length = new ByteData(4)..setUint32(0, bytes.length, Endianness.LITTLE_ENDIAN); - outputFile.writeAsBytesSync(length.buffer.asUint8List(), mode: FileMode.APPEND); - outputFile.writeAsBytesSync(bytes, mode: FileMode.APPEND); + outputFile.writeFromSync(length.buffer.asUint8List()); + outputFile.writeFromSync(bytes); } // Represents a parsed .flx Bundle. Contains information from the bundle's @@ -70,13 +72,14 @@ class Bundle { this.path, this.manifest, contentBytes, - KeyPair keyPair: null + AsymmetricKeyPair keyPair: null }) : _contentBytes = contentBytes { assert(path != null); assert(manifest != null); assert(_contentBytes != null); manifestBytes = serializeManifest(manifest, keyPair?.publicKey, _contentBytes); signatureBytes = signManifest(manifestBytes, keyPair?.privateKey); + _openContentStream = () => new Stream.fromIterable(>[_contentBytes]); } final String path; @@ -84,9 +87,8 @@ class Bundle { List manifestBytes; Map manifest; - // File byte offset of the start of the zip content. Only valid when opened - // from a file. - int _contentOffset; + // Callback to open a Stream containing the bundle content data. + StreamOpener _openContentStream; // Zip content bytes. Only valid when created in memory. List _contentBytes; @@ -100,7 +102,8 @@ class Bundle { } signatureBytes = await _readBytesWithLength(file); manifestBytes = await _readBytesWithLength(file); - _contentOffset = await file.position(); + int contentOffset = await file.position(); + _openContentStream = () => new File(path).openRead(contentOffset); file.close(); String manifestString = UTF8.decode(manifestBytes); @@ -115,14 +118,12 @@ class Bundle { return bundle; } - // When opened from a file, verifies that the package has a valid signature - // and content. + // Verifies that the package has a valid signature and content. Future verifyContent() async { - assert(_contentOffset != null); if (!verifyManifestSignature(manifest, manifestBytes, signatureBytes)) return false; - Stream> content = await new File(path).openRead(_contentOffset); + Stream> content = _openContentStream(); BigInteger expectedHash = new BigInteger(manifest['content-hash'], 10); if (!await verifyContentHash(expectedHash, content)) return false; @@ -133,10 +134,11 @@ class Bundle { // Writes the in-memory representation to disk. void writeSync() { assert(_contentBytes != null); - File outputFile = new File(path); - outputFile.writeAsStringSync('#!mojo mojo:sky_viewer\n'); + RandomAccessFile outputFile = new File(path).openSync(mode: FileMode.WRITE); + outputFile.writeStringSync('#!mojo mojo:sky_viewer\n'); _writeBytesWithLengthSync(outputFile, signatureBytes); _writeBytesWithLengthSync(outputFile, manifestBytes); - outputFile.writeAsBytesSync(_contentBytes, mode: FileMode.APPEND, flush: true); + outputFile.writeFromSync(_contentBytes); + outputFile.close(); } } diff --git a/engine/src/flutter/sky/packages/flx/lib/signing.dart b/engine/src/flutter/sky/packages/flx/lib/signing.dart index c9a1a0668fc..8ec65c842c9 100644 --- a/engine/src/flutter/sky/packages/flx/lib/signing.dart +++ b/engine/src/flutter/sky/packages/flx/lib/signing.dart @@ -12,6 +12,8 @@ import 'package:bignum/bignum.dart'; import 'package:cipher/cipher.dart'; import 'package:cipher/impl/client.dart'; +export 'package:cipher/cipher.dart' show AsymmetricKeyPair; + // The ECDSA algorithm parameters we're using. These match the parameters used // by the Flutter updater package. class CipherParameters { @@ -143,26 +145,32 @@ ECPublicKey _publicKeyFromPrivateKey(ECPrivateKey privateKey) { return new ECPublicKey(Q, privateKey.parameters); } -class KeyPair { - KeyPair(this.publicKey, this.privateKey); +AsymmetricKeyPair keyPairFromPrivateKeyFileSync(String privateKeyPath) { + File file = new File(privateKeyPath); + if (!file.existsSync()) + return null; + return keyPairFromPrivateKeyBytes(file.readAsBytesSync()); +} - ECPublicKey publicKey; - ECPrivateKey privateKey; +AsymmetricKeyPair keyPairFromPrivateKeyBytes(List privateKeyBytes) { + ECPrivateKey privateKey = _asn1ParsePrivateKey( + _params.domain, new Uint8List.fromList(privateKeyBytes)); + if (privateKey == null) + return null; + + ECPublicKey publicKey = _publicKeyFromPrivateKey(privateKey); + return new AsymmetricKeyPair(publicKey, privateKey); +} + +// TODO(mpcomplete): remove this class when flutter_tools is updated. +class KeyPair extends AsymmetricKeyPair { + KeyPair(PublicKey publicKey, PrivateKey privateKey) + : super(publicKey, privateKey); static KeyPair readFromPrivateKeySync(String privateKeyPath) { - File file = new File(privateKeyPath); - if (!file.existsSync()) + AsymmetricKeyPair pair = keyPairFromPrivateKeyFileSync(privateKeyPath); + if (pair == null) return null; - return fromPrivateKeyBytes(file.readAsBytesSync()); - } - - static KeyPair fromPrivateKeyBytes(List privateKeyBytes) { - ECPrivateKey privateKey = _asn1ParsePrivateKey( - _params.domain, new Uint8List.fromList(privateKeyBytes)); - if (privateKey == null) - return null; - - ECPublicKey publicKey = _publicKeyFromPrivateKey(privateKey); - return new KeyPair(publicKey, privateKey); + return new KeyPair(pair.publicKey, pair.privateKey); } }