Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
f9e43f12
Commit
f9e43f12
authored
Oct 19, 2015
by
Matt Perry
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1612 from mpcomplete/signer.cipher
Add support for verifying .flx signatures when updating
parents
734240db
dff3fa7e
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
208 additions
and
33 deletions
+208
-33
sky.yaml
examples/fitness/sky.yaml
+1
-1
sky.yaml
examples/stocks/sky.yaml
+3
-0
pubspec.yaml
packages/flutter/pubspec.yaml
+1
-1
bundle.dart
packages/updater/lib/bundle.dart
+75
-0
main.dart
packages/updater/lib/main.dart
+119
-24
pipe_to_file.dart
packages/updater/lib/pipe_to_file.dart
+5
-5
version.dart
packages/updater/lib/version.dart
+2
-2
pubspec.yaml
packages/updater/pubspec.yaml
+2
-0
No files found.
examples/fitness/sky.yaml
View file @
f9e43f12
name
:
fitness
version
:
0.0.1
update
_
url
:
http://localhost:9888/examples/fitness/
update
-
url
:
http://localhost:9888/examples/fitness/
material-design-icons
:
-
name
:
action/assessment
-
name
:
action/help
...
...
examples/stocks/sky.yaml
View file @
f9e43f12
name
:
stocks
version
:
0.0.2
update-url
:
http://localhost:9888/examples/stocks/
material-design-icons
:
-
name
:
action/account_balance
-
name
:
action/assessment
...
...
packages/flutter/pubspec.yaml
View file @
f9e43f12
...
...
@@ -11,7 +11,7 @@ dependencies:
newton
:
'
>=0.1.4
<0.2.0'
sky_engine
:
0.0.38
sky_services
:
0.0.38
sky_tools
:
'
>=0.0.2
0
<0.1.0'
sky_tools
:
'
>=0.0.2
5
<0.1.0'
vector_math
:
'
>=1.4.3
<2.0.0'
intl
:
'
>=0.12.4+2
<0.13.0'
environment
:
...
...
packages/updater/lib/bundle.dart
0 → 100644
View file @
f9e43f12
// 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
;
}
}
packages/updater/lib/main.dart
View file @
f9e43f12
...
...
@@ -3,19 +3,38 @@
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:convert'
;
import
'dart:math'
;
import
'dart:io'
;
import
'dart:typed_data'
;
import
'package:mojo/core.dart'
;
import
'package:flutter/services.dart'
;
// TODO(mpcomplete): Remove this 'hide' when we remove the conflicting
// UpdateService from activity.mojom.
import
'package:flutter/services.dart'
hide
UpdateServiceProxy
;
import
'package:sky_services/updater/update_service.mojom.dart'
;
import
'package:path/path.dart'
as
path
;
import
'package:yaml/yaml.dart'
as
yaml
;
import
'package:asn1lib/asn1lib.dart'
;
import
'package:bignum/bignum.dart'
;
import
'package:cipher/cipher.dart'
;
import
'package:cipher/impl/client.dart'
;
import
'
version
.dart'
;
import
'
bundle
.dart'
;
import
'pipe_to_file.dart'
;
import
'version.dart'
;
const
String
kManifestFile
=
'sky.yaml'
;
const
String
kBundleFile
=
'app.flx'
;
const
String
kManifestFile
=
'ui.yaml'
;
const
String
kBundleFile
=
'app.skyx'
;
// Number of bytes to read at a time from a file.
const
int
kReadBlockSize
=
32
*
1024
;
// The ECDSA algorithm parameters we're using. These match the parameters used
// by the signing tool in flutter_tools.
final
ECDomainParameters
_ecDomain
=
new
ECDomainParameters
(
'prime256v1'
);
final
String
kSignerAlgorithm
=
'SHA-256/ECDSA'
;
final
String
kHashAlgorithm
=
'SHA-256'
;
UpdateServiceProxy
_initUpdateService
(
)
{
UpdateServiceProxy
updateService
=
new
UpdateServiceProxy
.
unbound
();
...
...
@@ -32,20 +51,45 @@ Future<String> getDataDir() async {
return
cachedDataDir
;
}
// Parses a DER-encoded ASN.1 ECDSA signature block.
ECSignature
_asn1ParseSignature
(
Uint8List
signature
)
{
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
);
}
class
UpdateFailure
extends
Error
{
UpdateFailure
(
this
.
_message
);
String
_message
;
String
toString
()
=>
_message
;
}
class
UpdateTask
{
UpdateTask
()
{}
UpdateTask
()
;
run
()
async
{
Future
run
()
async
{
try
{
await
_runImpl
();
}
catch
(
e
)
{
}
on
UpdateFailure
catch
(
e
)
{
print
(
'Update failed:
$e
'
);
}
catch
(
e
,
stackTrace
)
{
print
(
'Update failed:
$e
'
);
print
(
'Stack:
$stackTrace
'
);
}
finally
{
_updateService
.
ptr
.
notifyUpdateCheckComplete
();
}
}
_runImpl
()
async
{
Future
_runImpl
()
async
{
_dataDir
=
await
getDataDir
();
await
_readLocalManifest
();
...
...
@@ -54,27 +98,25 @@ class UpdateTask {
print
(
'Update skipped. No new version.'
);
return
;
}
MojoResult
result
=
await
_fetchBundle
();
if
(!
result
.
isOk
)
{
print
(
'Update failed while fetching new skyx bundle.'
);
return
;
}
await
_fetchBundle
();
await
_validateBundle
();
await
_replaceBundle
();
print
(
'Update success.'
);
}
yaml
.
Yaml
Map
_currentManifest
;
Map
_currentManifest
;
String
_dataDir
;
String
_tempPath
;
_readLocalManifest
()
async
{
String
manifestPath
=
path
.
join
(
_dataDir
,
kManifestFile
);
String
manifestData
=
await
new
File
(
manifestPath
).
readAsString
();
_currentManifest
=
yaml
.
loadYaml
(
manifestData
,
sourceUrl:
manifestPath
);
Future
_readLocalManifest
()
async
{
String
bundlePath
=
path
.
join
(
_dataDir
,
kBundleFile
);
Bundle
bundle
=
await
Bundle
.
readHeader
(
bundlePath
);
_currentManifest
=
bundle
.
manifest
;
bundle
.
content
.
close
();
}
Future
<
yaml
.
YamlMap
>
_fetchManifest
()
async
{
String
manifestUrl
=
_currentManifest
[
'update
_
url'
]
+
'/'
+
kManifestFile
;
String
manifestUrl
=
_currentManifest
[
'update
-
url'
]
+
'/'
+
kManifestFile
;
String
manifestData
=
await
fetchString
(
manifestUrl
);
return
yaml
.
loadYaml
(
manifestData
,
sourceUrl:
manifestUrl
);
}
...
...
@@ -85,21 +127,74 @@ class UpdateTask {
return
(
currentVersion
<
remoteVersion
);
}
Future
<
MojoResult
>
_fetchBundle
()
async
{
Future
_fetchBundle
()
async
{
// TODO(mpcomplete): Use the cache dir. We need an equivalent of mkstemp().
_tempPath
=
path
.
join
(
_dataDir
,
'tmp.skyx'
);
String
bundleUrl
=
_currentManifest
[
'update
_
url'
]
+
'/'
+
kBundleFile
;
String
bundleUrl
=
_currentManifest
[
'update
-
url'
]
+
'/'
+
kBundleFile
;
UrlResponse
response
=
await
fetchUrl
(
bundleUrl
);
return
PipeToFile
.
copyToFile
(
response
.
body
,
_tempPath
);
MojoResult
result
=
await
PipeToFile
.
copyToFile
(
response
.
body
,
_tempPath
);
if
(!
result
.
isOk
)
throw
new
UpdateFailure
(
'Failure fetching new package:
${response.statusLine}
'
);
}
Future
_validateBundle
()
async
{
Bundle
bundle
=
await
Bundle
.
readHeader
(
_tempPath
);
if
(
bundle
==
null
)
throw
new
UpdateFailure
(
'Remote package not a valid FLX file.'
);
if
(
bundle
.
manifest
[
'key'
]
!=
_currentManifest
[
'key'
])
throw
new
UpdateFailure
(
'Remote package key does not match.'
);
await
_verifyManifestSignature
(
bundle
);
await
_verifyContentHash
(
bundle
);
bundle
.
content
.
close
();
}
Future
_verifyManifestSignature
(
Bundle
bundle
)
async
{
ECSignature
ecSignature
=
_asn1ParseSignature
(
bundle
.
signatureBytes
);
if
(
ecSignature
==
null
)
throw
new
UpdateFailure
(
'Corrupt package signature.'
);
List
keyBytes
=
BASE64
.
decode
(
_currentManifest
[
'key'
]);
ECPoint
q
=
_ecDomain
.
curve
.
decodePoint
(
keyBytes
);
ECPublicKey
ecPublicKey
=
new
ECPublicKey
(
q
,
_ecDomain
);
Signer
signer
=
new
Signer
(
kSignerAlgorithm
);
signer
.
init
(
false
,
new
PublicKeyParameter
(
ecPublicKey
));
if
(!
signer
.
verifySignature
(
bundle
.
manifestBytes
,
ecSignature
))
throw
new
UpdateFailure
(
'Invalid package signature. This package has been tampered with.'
);
}
Future
_verifyContentHash
(
Bundle
bundle
)
async
{
// Hash the bundle contents.
Digest
hasher
=
new
Digest
(
kHashAlgorithm
);
RandomAccessFile
content
=
bundle
.
content
;
int
remainingLen
=
await
content
.
length
()
-
await
content
.
position
();
while
(
remainingLen
>
0
)
{
List
<
int
>
chunk
=
await
content
.
read
(
min
(
remainingLen
,
kReadBlockSize
));
hasher
.
update
(
chunk
,
0
,
chunk
.
length
);
remainingLen
-=
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
);
// Compare to our expected hash from the manifest.
BigInteger
expectedHash
=
new
BigInteger
(
bundle
.
manifest
[
'content-hash'
],
10
);
if
(
expectedHash
!=
actualHash
)
throw
new
UpdateFailure
(
'Invalid package content hash. This package has been tampered with.'
);
}
_replaceBundle
()
async
{
Future
_replaceBundle
()
async
{
String
bundlePath
=
path
.
join
(
_dataDir
,
kBundleFile
);
await
new
File
(
_tempPath
).
rename
(
bundlePath
);
}
}
void
main
(
)
{
var
task
=
new
UpdateTask
();
initCipher
();
UpdateTask
task
=
new
UpdateTask
();
task
.
run
();
}
packages/updater/lib/pipe_to_file.dart
View file @
f9e43f12
...
...
@@ -30,13 +30,13 @@ class PipeToFile {
return
_consumer
.
endRead
(
thisRead
.
lengthInBytes
);
}
Future
<
MojoResult
>
drain
()
async
{
va
r
completer
=
new
Completer
();
Future
drain
()
async
{
Complete
r
completer
=
new
Completer
();
// TODO(mpcomplete): Is it legit to pass an async callback to listen?
_eventStream
.
listen
((
List
<
int
>
event
)
async
{
var
mojoSignals
=
new
MojoHandleSignals
(
event
[
1
]);
MojoHandleSignals
mojoSignals
=
new
MojoHandleSignals
(
event
[
1
]);
if
(
mojoSignals
.
isReadable
)
{
var
result
=
await
_doRead
();
MojoResult
result
=
await
_doRead
();
if
(!
result
.
isOk
)
{
_eventStream
.
close
();
_eventStream
=
null
;
...
...
@@ -58,7 +58,7 @@ class PipeToFile {
}
static
Future
<
MojoResult
>
copyToFile
(
MojoDataPipeConsumer
consumer
,
String
outputPath
)
{
var
drainer
=
new
PipeToFile
(
consumer
,
outputPath
);
PipeToFile
drainer
=
new
PipeToFile
(
consumer
,
outputPath
);
return
drainer
.
drain
();
}
}
packages/updater/lib/version.dart
View file @
f9e43f12
...
...
@@ -9,7 +9,7 @@ import 'dart:math';
// Usage: assert(new Version('1.1.0') < new Version('1.2.1'));
class
Version
{
Version
(
String
versionStr
)
:
_parts
=
versionStr
.
split
(
'.'
).
map
((
val
)
=>
int
.
parse
(
val
)).
toList
();
_parts
=
versionStr
.
split
(
'.'
).
map
((
String
val
)
=>
int
.
parse
(
val
)).
toList
();
List
<
int
>
_parts
;
...
...
@@ -28,5 +28,5 @@ class Version {
return
_parts
.
length
-
other
.
_parts
.
length
;
// results in 1.0 < 1.0.0
}
int
get
hashCode
=>
_parts
.
fold
(
373
,
(
acc
,
part
)
=>
37
*
acc
+
part
);
int
get
hashCode
=>
_parts
.
fold
(
373
,
(
int
acc
,
int
part
)
=>
37
*
acc
+
part
);
}
packages/updater/pubspec.yaml
View file @
f9e43f12
...
...
@@ -9,6 +9,8 @@ dependencies:
sky_services
:
any
path
:
any
yaml
:
any
cipher
:
any
asn1lib
:
any
dependency_overrides
:
flutter
:
path
:
../sky
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment