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..323cd8c5cd --- /dev/null +++ b/packages/flx/pubspec.yaml @@ -0,0 +1,13 @@ +name: flx +version: 0.0.1 +author: Flutter Authors +description: Library for dealing with Flutter bundle (.flx) files +homepage: http://flutter.io +dependencies: + 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'