Merge pull request #1722 from mpcomplete/flx.1
Create new 'flx' package. Just a copy for now.
This commit is contained in:
commit
63dbae7eb0
75
packages/flx/lib/bundle.dart
Normal file
75
packages/flx/lib/bundle.dart
Normal file
@ -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<List<int>> _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<String> _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 <any string>\n
|
||||
// <32-bit length><signature of the manifest data>
|
||||
// <32-bit length><manifest data>
|
||||
// <zip content>
|
||||
//
|
||||
// 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 <zip content>.
|
||||
class Bundle {
|
||||
Bundle(this.path);
|
||||
|
||||
final String path;
|
||||
List<int> signatureBytes;
|
||||
List<int> manifestBytes;
|
||||
Map<String, dynamic> manifest;
|
||||
RandomAccessFile content;
|
||||
|
||||
Future<bool> _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<Bundle> readHeader(String path) async {
|
||||
Bundle bundle = new Bundle(path);
|
||||
if (!await bundle._readHeader())
|
||||
return null;
|
||||
return bundle;
|
||||
}
|
||||
}
|
94
packages/flx/lib/signing.dart
Normal file
94
packages/flx/lib/signing.dart
Normal file
@ -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<String> 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<int> 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<ECPrivateKey> loadPrivateKey(String privateKeyPath) async {
|
||||
File file = new File(privateKeyPath);
|
||||
if (!file.existsSync())
|
||||
return null;
|
||||
List<int> 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);
|
||||
}
|
13
packages/flx/pubspec.yaml
Normal file
13
packages/flx/pubspec.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
name: flx
|
||||
version: 0.0.1
|
||||
author: Flutter Authors <flutter-dev@googlegroups.com>
|
||||
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'
|
Loading…
x
Reference in New Issue
Block a user