corsac-dart / jwt

Lightweight implementation of JSON Web Tokens (JWT)
BSD 2-Clause "Simplified" License
45 stars 28 forks source link

addition: ES256 Signer #5

Open shrugs opened 5 years ago

shrugs commented 5 years ago

I've written an ES256 signer that I'm using in a personal project. Unfortunately I don't have the time to write a full pull request with tests, but I'd like to make the code available for searchers or a future implementor.

The signature encode/decode comes from crypto_keys, which might have additional LICENSE restrictions.

import 'dart:typed_data';

import "package:pointycastle/pointycastle.dart";
import 'package:corsac_jwt/corsac_jwt.dart';

class ES256Signer implements JWTSigner {
  String get algorithm => 'ES256';

  final AsymmetricKeyPair<PublicKey, PrivateKey> pair;

  ES256Signer({
    this.pair,
  });

  @override
  List<int> sign(List<int> data) {
    final pcSigner = Signer("SHA-256/DET-ECDSA");
    pcSigner.init(
      true,
      PrivateKeyParameter(pair.privateKey),
    );
    final ECSignature sig = pcSigner.generateSignature(data);
    var length = 32;
    var bytes = Uint8List(length * 2);
    bytes.setRange(0, length, _bigIntToBytes(sig.r, length).toList().reversed);
    bytes.setRange(
        length, length * 2, _bigIntToBytes(sig.s, length).toList().reversed);
    return bytes;
  }

  @override
  bool verify(List<int> data, List<int> signature) {
    final verifier = Signer("SHA-256/DET-ECDSA");
    verifier.init(false, PublicKeyParameter(pair.publicKey));
    final sig = ECSignature(
      _bigIntFromBytes(signature.take(32)),
      _bigIntFromBytes(signature.skip(32)),
    );
    return verifier.verifySignature(data, sig);
  }
}
®c
final _b256 = BigInt.from(256);

Iterable<int> _bigIntToBytes(BigInt v, int length) sync* {
  for (var i = 0; i < length; i++) {
    yield (v % _b256).toInt();
    v = v ~/ _b256;
  }
}

BigInt _bigIntFromBytes(Iterable<int> bytes) {
  return bytes.fold(BigInt.zero, (a, b) => a * _b256 + BigInt.from(b));
}
pulyaevskiy commented 5 years ago

Thanks for the example!

Just wondering which package/tool are you using to read/decode public and private keys in your project? I used rsa_pkcs but it's not maintained anymore, unfortunately.

shrugs commented 5 years ago

😅 I just wrote my own super hackily; private keys are just bigints, so .toString/.parse and for public keys I encode them as uncompressed octet strings like

const String kUncompressedPrefix = "4";

/// serializes public key as uncompressed octect string
String serializePublicKey(ECPublicKey publicKey) {
  return kUncompressedPrefix +
      bigIntToHexString(publicKey.Q.x.toBigInteger(), 64) +
      bigIntToHexString(publicKey.Q.y.toBigInteger(), 64);
}

String bigIntToHexString(BigInt n, int length) => n.toRadixString(16).padLeft(length, '0');

which is all I need for my app (using them as identifiers for auth). But I was very annoyed at the lack of an obvious mechanism of encoding them for use with other libraries; I had to dig into the ruby openssl source docs to see what it expected and reverse engineer it from there.