// 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'; 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 { final String signerAlgorithm = 'SHA-256/ECDSA'; final String hashAlgorithm = 'SHA-256'; final ECDomainParameters domain = new ECDomainParameters('prime256v1'); SecureRandom get random { if (_random == null) _initRandom(new Uint8List(16), new Uint8List(16)); return _random; } // Seeds our secure random number generator using data from /dev/urandom. // Disclaimer: I don't really understand why we need 2 parameters for // cipher's API. Future seedRandom() async { try { RandomAccessFile file = await new File("/dev/urandom").open(); Uint8List key = new Uint8List.fromList(await file.read(16)); Uint8List iv = new Uint8List.fromList(await file.read(16)); _initRandom(key, iv); } on FileSystemException { // TODO(mpcomplete): need an entropy source on Windows. We might get this // for free from Dart itself soon. print("Warning: Failed to seed random number generator. No /dev/urandom."); } } SecureRandom _random; void _initRandom(Uint8List key, Uint8List iv) { KeyParameter keyParam = new KeyParameter(key); ParametersWithIV params = new ParametersWithIV(keyParam, iv); _random = new SecureRandom('AES/CTR/AUTO-SEED-PRNG') ..seed(params); } static CipherParameters get() => _params; static CipherParameters _init() { initCipher(); return new CipherParameters(); } } final CipherParameters _params = CipherParameters._init(); // 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(_params.hashAlgorithm).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. Uint8List signManifest(Uint8List manifestBytes, ECPrivateKey privateKey) { if (manifestBytes == null || privateKey == null) return new Uint8List(0); Signer signer = new Signer(_params.signerAlgorithm); PrivateKeyParameter params = new PrivateKeyParameter(privateKey); signer.init(true, new ParametersWithRandom(params, _params.random)); ECSignature signature = signer.generateSignature(manifestBytes); ASN1Sequence asn1 = new ASN1Sequence() ..add(new ASN1Integer(signature.r)) ..add(new ASN1Integer(signature.s)); return asn1.encodedBytes; } bool verifyManifestSignature(Map manifest, Uint8List manifestBytes, Uint8List signatureBytes) { ECSignature signature = _asn1ParseSignature(signatureBytes); if (signature == null) return false; List keyBytes = BASE64.decode(manifest['key']); ECPoint q = _params.domain.curve.decodePoint(keyBytes); ECPublicKey publicKey = new ECPublicKey(q, _params.domain); Signer signer = new Signer(_params.signerAlgorithm); signer.init(false, new PublicKeyParameter(publicKey)); return signer.verifySignature(manifestBytes, signature); } Future verifyContentHash(BigInteger expectedHash, Stream> content) async { // Hash the file incrementally. Digest hasher = new Digest(_params.hashAlgorithm); await content.forEach((List chunk) { hasher.update(chunk, 0, chunk.length); }); Uint8List hashBytes = new Uint8List(hasher.digestSize); int len = hasher.doFinal(hashBytes, 0); hashBytes = hashBytes.sublist(0, len); BigInteger actualHash = new BigInteger.fromBytes(1, hashBytes); return expectedHash == actualHash; } // Parses a DER-encoded ASN.1 ECDSA private key block. 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); } // Parses a DER-encoded ASN.1 ECDSA signature block. ECSignature _asn1ParseSignature(Uint8List signature) { 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; } } ECPublicKey _publicKeyFromPrivateKey(ECPrivateKey privateKey) { ECPoint Q = privateKey.parameters.G * privateKey.d; return new ECPublicKey(Q, privateKey.parameters); } AsymmetricKeyPair keyPairFromPrivateKeyFileSync(String privateKeyPath) { File file = new File(privateKeyPath); if (!file.existsSync()) return null; return keyPairFromPrivateKeyBytes(file.readAsBytesSync()); } 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); }