-
-
Save killermonk/92644b4fb25140cb02be17a9ffb45b26 to your computer and use it in GitHub Desktop.
This is a script to sign data using the crypto_keys dart package and verify the signed data using OpenSSL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
import 'package:asn1lib/asn1lib.dart'; | |
import 'package:byte_extensions/byte_extensions.dart'; | |
import 'package:crypto/crypto.dart'; | |
import 'package:crypto_keys/crypto_keys.dart'; | |
Future<void> writeFileData(File f, List<int> data) async { | |
await f.writeAsBytes(data); | |
} | |
Uint8List encodePublicKey(EcPublicKey key) { | |
final keyInfo = ASN1Sequence() | |
// alg info | |
..add(ASN1Sequence() | |
// ecPublicKey | |
..add(ASN1ObjectIdentifier.fromComponentString('1.2.840.10045.2.1')) | |
// prime256v1 | |
..add(ASN1ObjectIdentifier.fromComponentString('1.2.840.10045.3.1.7'))) | |
// PK: 0x04 x y | |
..add(ASN1BitString(Uint8List.fromList([ | |
0x04, | |
...key.xCoordinate.asBytes(maxBytes: 32), | |
...key.yCoordinate.asBytes(maxBytes: 32), | |
]))); | |
return keyInfo.encodedBytes; | |
} | |
String pemEncodePublicKey(Uint8List der) { | |
final encoded = base64.encode(der); | |
final split = RegExp(r'.{1,64}').allMatches(encoded).map((m) => m.group(0)); | |
const header = '-----BEGIN PUBLIC KEY-----\r\n'; | |
const footer = '\r\n-----END PUBLIC KEY-----'; | |
return '$header${split.join('\r\n')}$footer'; | |
} | |
Uint8List encodePrivateKey(KeyPair keypair) { | |
final pk = keypair.privateKey as EcPrivateKey; | |
final pub = keypair.publicKey as EcPublicKey; | |
final der = ASN1Sequence() | |
..add(ASN1Integer.fromInt(1)) | |
..add(ASN1OctetString(pk.eccPrivateKey.asBytes(maxBytes: 32))) | |
// protection | |
..add(ASN1Object.fromBytes(Uint8List.fromList([0xa0, 0x0a]))) | |
// prime256v1 / P-256 | |
..add(ASN1ObjectIdentifier.fromComponentString('1.2.840.10045.3.1.7')) | |
// extra certs | |
..add( | |
ASN1Sequence(tag: 0xa1) | |
// PK: 0x04 x y | |
..add(ASN1BitString(Uint8List.fromList([ | |
0x04, | |
...pub.xCoordinate.asBytes(maxBytes: 32), | |
...pub.yCoordinate.asBytes(maxBytes: 32), | |
]))), | |
); | |
return der.encodedBytes; | |
} | |
String pemEncodePrivateKey(Uint8List der) { | |
final encoded = base64.encode(der); | |
final split = RegExp(r'.{1,64}').allMatches(encoded).map((m) => m.group(0)); | |
const header = '-----BEGIN EC PRIVATE KEY-----\r\n'; | |
const footer = '\r\n-----END EC PRIVATE KEY-----'; | |
return '$header${split.join('\r\n')}$footer'; | |
} | |
Uint8List signatureToDER(Uint8List sig) { | |
final r = sig.sublist(0, 0x20); | |
final s = sig.sublist(0x20); | |
final der = ASN1Sequence() | |
..add(ASN1Integer.fromBytes(Uint8List.fromList([0x02, r.length, ...r]))) | |
..add(ASN1Integer.fromBytes(Uint8List.fromList([0x02, s.length, ...s]))); | |
return der.encodedBytes; | |
} | |
void main(List<String> arguments) async { | |
var iters = 1; | |
if (arguments.isNotEmpty) { | |
iters = int.tryParse(arguments[0]) ?? 1; | |
} | |
final challenges = [ | |
'-TIx3zPnT3UPRE65tf5GyWSk4pVxk5yEUm45Rn8GR6s', | |
'z6R2ALSPB0SH_6ANOVSHZjRVGm_eL-IMHBdxCJ3j6i8', | |
's--tC43ZfQk9RfIjWUXyTUWIEy5rAhkX6AdiBwO1Iek', | |
'YnOj0cMa1ZzNuslzdirwHLoUI35A2VPhfRFNKf-CCLw', | |
]; | |
for (var i = 0; i < iters; i++) { | |
final keypair = KeyPair.generateEc(curves.p256); | |
final challenge = challenges[i % 4]; | |
final dataJsonData = [ | |
'"type"="webauthn.create"', | |
'"challenge"="$challenge"', | |
'"origin"="http://localhost:3000"', | |
'"crossOrigin":false' | |
]; | |
final dataJson = '{${dataJsonData.join(',')}}'; | |
final toSign = BytesBuilder() | |
// Authenticator Data | |
..add([0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, 0x8c, 0x68]) | |
..add([0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, 0x60, 0x5b]) | |
..add([0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7]) | |
..add([0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, 0x97, 0x63]) | |
..add([0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) | |
..add([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) | |
..add([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x9f]) | |
..add([0x06, 0x98, 0x03, 0x13, 0xbd, 0x58, 0xaa, 0x37]) | |
..add([0x92, 0x99, 0x6d, 0xd4, 0xb1, 0x4d, 0x9b, 0xf6]) | |
..add([0xd3, 0xe8, 0xee, 0xef, 0x16, 0x2d, 0xa5, 0x5f]) | |
..add([0xba, 0xa9, 0x31, 0x17, 0x86, 0xf7, 0xb1, 0xa5]) | |
..add([0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58]) | |
..add([0x20, 0x23, 0xde, 0xac, 0x03, 0xf2, 0x2c, 0x77]) | |
..add([0x64, 0x38, 0x31, 0xa8, 0x5f, 0xd0, 0x8b, 0x34]) | |
..add([0xe8, 0x54, 0xf7, 0x2a, 0x45, 0x92, 0xec, 0xd0]) | |
..add([0xa4, 0x19, 0x9c, 0x7e, 0x31, 0xb3, 0x87, 0x9f]) | |
..add([0x72, 0x22, 0x58, 0x20, 0xc8, 0x16, 0x35, 0x39]) | |
..add([0xe0, 0xe0, 0x57, 0x3a, 0x2e, 0xaa, 0xf3, 0x66]) | |
..add([0x23, 0x18, 0x64, 0xf8, 0xf5, 0xad, 0x1e, 0x97]) | |
..add([0xc9, 0x18, 0xcb, 0x3b, 0x29, 0x43, 0xbc, 0x5d]) | |
..add([0xe4, 0xf6, 0xe3, 0x9e]) | |
// Client Data Hash | |
..add(sha256.convert(utf8.encode(dataJson)).bytes); | |
final tmpDir = Directory.systemTemp.createTempSync('keytest'); | |
final tmpPriv = File('${tmpDir.path}/private.pem'); | |
final tmpPub = File('${tmpDir.path}/public.pem'); | |
final tmpData = File('${tmpDir.path}/data.bin'); | |
var verified = true; | |
for (var s = 0; s < 4; s++) { | |
final signer = keypair.createSigner(algorithms.signing.ecdsa.sha256); | |
final signature = signer.sign(toSign.toBytes()); | |
final signatureDer = signatureToDER(signature.data); | |
final tmpSig = File('${tmpDir.path}/sig$s.bin'); | |
await writeFileData( | |
tmpPriv, | |
utf8.encode(pemEncodePrivateKey(encodePrivateKey(keypair))), | |
); | |
await writeFileData( | |
tmpPub, | |
utf8.encode(pemEncodePublicKey( | |
encodePublicKey(keypair.publicKey as EcPublicKey))), | |
); | |
await writeFileData(tmpData, toSign.toBytes()); | |
await writeFileData(tmpSig, signatureDer); | |
final openVerify = Process.runSync('openssl', [ | |
'dgst', | |
'-sha256', | |
'-verify', | |
tmpPub.path, | |
'-signature', | |
tmpSig.path, | |
tmpData.path, | |
]); | |
if (openVerify.exitCode != 0) { | |
verified = false; | |
stdout.write( | |
'Openssl verify failed (${tmpSig.path}): ${openVerify.stderr}'); | |
} else { | |
stdout.write( | |
'Openssl verify succeeded (${tmpSig.path}): ${openVerify.stdout}'); | |
} | |
} | |
// Failure | |
if (!verified) { | |
final tmpOpensslSig = File('${tmpDir.path}/validsig.bin'); | |
Process.runSync('openssl', [ | |
'dgst', | |
'-sha256', | |
'-sign', | |
tmpPriv.path, | |
'-out', | |
tmpOpensslSig.path, | |
tmpData.path, | |
]); | |
} else { | |
// tmpDir.deleteSync(recursive: true); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment