blob: 36bbc7f63b710b88871828c912c3eaa35da4a28e [file] [log] [blame]
// 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' hide BASE64;
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';
import 'package:crypto/crypto.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<String> kSavedKeys = <String>[
'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<String, dynamic> manifest,
Uint8List manifestBytes,
Uint8List signatureBytes) {
ECSignature signature = _asn1ParseSignature(signatureBytes);
if (signature == null)
return false;
List<int> 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<bool> verifyContentHash(BigInteger expectedHash, Stream<List<int>> content) async {
// Hash the file incrementally.
Digest hasher = new Digest(_params.hashAlgorithm);
await content.forEach((List<int> 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<int> privateKeyBytes) {
ECPrivateKey privateKey = _asn1ParsePrivateKey(
_params.domain, new Uint8List.fromList(privateKeyBytes));
if (privateKey == null)
return null;
ECPublicKey publicKey = _publicKeyFromPrivateKey(privateKey);
return new AsymmetricKeyPair(publicKey, privateKey);
}