From 104e125d932cb2c344355103a7ac341ed2570f75 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Wed, 21 Oct 2015 17:20:25 -0400 Subject: [PATCH 1/2] Create new 'flx' package. Just a copy for now. This will be shared by sky/updater and flutter_tools. Currently bundle.dart and signing.dart are unmodified copies of the files from updater and flutter_tools, respectively. This is so future changes are easier to see. --- packages/flx/lib/bundle.dart | 75 ++++++++++++++++++++++++++++ packages/flx/lib/signing.dart | 94 +++++++++++++++++++++++++++++++++++ packages/flx/pubspec.yaml | 18 +++++++ 3 files changed, 187 insertions(+) create mode 100644 packages/flx/lib/bundle.dart create mode 100644 packages/flx/lib/signing.dart create mode 100644 packages/flx/pubspec.yaml diff --git a/packages/flx/lib/bundle.dart b/packages/flx/lib/bundle.dart new file mode 100644 index 0000000000..9e72a25921 --- /dev/null +++ b/packages/flx/lib/bundle.dart @@ -0,0 +1,75 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +const String kBundleMagic = '#!mojo '; + +Future> _readBytesWithLength(RandomAccessFile file) async { + ByteData buffer = new ByteData(4); + await file.readInto(buffer.buffer.asUint8List()); + int length = buffer.getUint32(0, Endianness.LITTLE_ENDIAN); + return await file.read(length); +} + +const int kMaxLineLen = 10*1024; +const int kNewline = 0x0A; +Future _readLine(RandomAccessFile file) async { + String line = ''; + while (line.length < kMaxLineLen) { + int byte = await file.readByte(); + if (byte == -1 || byte == kNewline) + break; + line += new String.fromCharCode(byte); + } + return line; +} + +// Represents a parsed .flx Bundle. Contains information from the bundle's +// header, as well as an open File handle positioned where the zip content +// begins. +// The bundle format is: +// #!mojo \n +// <32-bit length> +// <32-bit length> +// +// +// The manifest is a JSON string containing the following keys: +// (optional) name: the name of the package. +// version: the package version. +// update-url: the base URL to download a new manifest and bundle. +// key: a BASE-64 encoded DER-encoded ASN.1 representation of the Q point of the +// ECDSA public key that was used to sign this manifest. +// content-hash: an integer SHA-256 hash value of the . +class Bundle { + Bundle(this.path); + + final String path; + List signatureBytes; + List manifestBytes; + Map manifest; + RandomAccessFile content; + + Future _readHeader() async { + content = await new File(path).open(); + String magic = await _readLine(content); + if (!magic.startsWith(kBundleMagic)) + return false; + signatureBytes = await _readBytesWithLength(content); + manifestBytes = await _readBytesWithLength(content); + String manifestString = UTF8.decode(manifestBytes); + manifest = JSON.decode(manifestString); + return true; + } + + static Future readHeader(String path) async { + Bundle bundle = new Bundle(path); + if (!await bundle._readHeader()) + return null; + return bundle; + } +} diff --git a/packages/flx/lib/signing.dart b/packages/flx/lib/signing.dart new file mode 100644 index 0000000000..d3390a6d59 --- /dev/null +++ b/packages/flx/lib/signing.dart @@ -0,0 +1,94 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:asn1lib/asn1lib.dart'; +import 'package:bignum/bignum.dart'; +import 'package:cipher/cipher.dart'; + +// The ECDSA algorithm parameters we're using. These match the parameters used +// by the Flutter updater package. +final ECDomainParameters _ecDomain = new ECDomainParameters('prime256v1'); +final String kSignerAlgorithm = 'SHA-256/ECDSA'; +final String kHashAlgorithm = 'SHA-256'; + +final SecureRandom _random = _initRandom(); + +SecureRandom _initRandom() { + // TODO(mpcomplete): Provide a better seed here. External entropy source? + final Uint8List key = new Uint8List(16); + final KeyParameter keyParam = new KeyParameter(key); + final ParametersWithIV params = new ParametersWithIV(keyParam, new Uint8List(16)); + SecureRandom random = new SecureRandom('AES/CTR/AUTO-SEED-PRNG') + ..seed(params); + return random; +} + +// Returns a serialized manifest, with the public key and hash of the content +// included. +Uint8List serializeManifest(Map manifestDescriptor, ECPublicKey publicKey, Uint8List zipBytes) { + if (manifestDescriptor == null) + return null; + final List kSavedKeys = [ + 'name', + 'version', + 'update-url' + ]; + Map outputManifest = new Map(); + manifestDescriptor.forEach((key, value) { + if (kSavedKeys.contains(key)) + outputManifest[key] = value; + }); + + if (publicKey != null) + outputManifest['key'] = BASE64.encode(publicKey.Q.getEncoded()); + + Uint8List zipHash = new Digest(kHashAlgorithm).process(zipBytes); + BigInteger zipHashInt = new BigInteger.fromBytes(1, zipHash); + outputManifest['content-hash'] = zipHashInt.intValue(); + + return new Uint8List.fromList(UTF8.encode(JSON.encode(outputManifest))); +} + +// Returns the ASN.1 encoded signature of the input manifestBytes. +List signManifest(Uint8List manifestBytes, ECPrivateKey privateKey) { + if (manifestBytes == null || privateKey == null) + return []; + Signer signer = new Signer(kSignerAlgorithm); + PrivateKeyParameter params = new PrivateKeyParameter(privateKey); + signer.init(true, new ParametersWithRandom(params, _random)); + ECSignature signature = signer.generateSignature(manifestBytes); + ASN1Sequence asn1 = new ASN1Sequence() + ..add(new ASN1Integer(signature.r)) + ..add(new ASN1Integer(signature.s)); + return asn1.encodedBytes; +} + +ECPrivateKey _asn1ParsePrivateKey(ECDomainParameters ecDomain, Uint8List privateKey) { + ASN1Parser parser = new ASN1Parser(privateKey); + ASN1Sequence seq = parser.nextObject(); + assert(seq.elements.length >= 2); + ASN1OctetString keyOct = seq.elements[1]; + BigInteger d = new BigInteger.fromBytes(1, keyOct.octets); + return new ECPrivateKey(d, ecDomain); +} + +Future loadPrivateKey(String privateKeyPath) async { + File file = new File(privateKeyPath); + if (!file.existsSync()) + return null; + List bytes = file.readAsBytesSync(); + return _asn1ParsePrivateKey(_ecDomain, new Uint8List.fromList(bytes)); +} + +ECPublicKey publicKeyFromPrivateKey(ECPrivateKey privateKey) { + if (privateKey == null) + return null; + ECPoint Q = privateKey.parameters.G * privateKey.d; + return new ECPublicKey(Q, privateKey.parameters); +} diff --git a/packages/flx/pubspec.yaml b/packages/flx/pubspec.yaml new file mode 100644 index 0000000000..a1eb2a6f57 --- /dev/null +++ b/packages/flx/pubspec.yaml @@ -0,0 +1,18 @@ +name: updater +version: 0.0.1 +author: Flutter Authors +description: The autoupdater for flutter +homepage: http://flutter.io +dependencies: + mojo: '>=0.2.0 <0.3.0' + flutter: ">=0.0.3 <0.1.0" + sky_services: any + path: any + yaml: any + cipher: any + asn1lib: any +dependency_overrides: + flutter: + path: ../sky +environment: + sdk: '>=1.12.0 <2.0.0' From 6c007406637986c7192d8abc5edca38167697540 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Wed, 21 Oct 2015 17:55:20 -0400 Subject: [PATCH 2/2] deps --- packages/flx/pubspec.yaml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/flx/pubspec.yaml b/packages/flx/pubspec.yaml index a1eb2a6f57..323cd8c5cd 100644 --- a/packages/flx/pubspec.yaml +++ b/packages/flx/pubspec.yaml @@ -1,18 +1,13 @@ -name: updater +name: flx version: 0.0.1 author: Flutter Authors -description: The autoupdater for flutter +description: Library for dealing with Flutter bundle (.flx) files homepage: http://flutter.io dependencies: - mojo: '>=0.2.0 <0.3.0' - flutter: ">=0.0.3 <0.1.0" - sky_services: any - path: any - yaml: any - cipher: any - asn1lib: any -dependency_overrides: - flutter: - path: ../sky + sky_services: 0.0.38 + yaml: ^2.1.3 + asn1lib: ^0.4.1 + cipher: ^0.7.1 + path: ^1.3.0 environment: sdk: '>=1.12.0 <2.0.0'