Skip to content

Instantly share code, notes, and snippets.

@killermonk
Last active November 5, 2023 06:37
Show Gist options
  • Save killermonk/92644b4fb25140cb02be17a9ffb45b26 to your computer and use it in GitHub Desktop.
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
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