bcgit / pc-dart

Pointy Castle - Dart Derived Bouncy Castle APIs
MIT License
241 stars 125 forks source link

ECDSA Signature validation with base64 strings #159

Closed KODA-Christian closed 1 year ago

KODA-Christian commented 2 years ago

Hi! I need help verifying an EC signature which is a base64 string with the public key that is also a base64 string. Something like:

Signature: MEUCIBYmubxYx/Q5CWiKwbfxgZs8KXaB+KEW59izRU8IPbROAiEA3YPVuD2TxpJeQ8gZLUfFfNgrr96orTjTwEXy4mQQqqI=

Public Key: -----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEN3ISIC6bUzVqAfbHnAl7iIO3le9UAqYM wymvYSQTxQQWr8mgc498BRjZqtEfnpRn95DpCxMJwcvPkwm4PFo/jg== -----END PUBLIC KEY-----

Thanks in advance

tallinn1960 commented 2 years ago

The signature looks like DER-encoded ASN1. It is an ASN1 Sequence of the two ASN1 Integers r and s, which constitute an ECSignature.

  var sdecoded = base64Decode(signature);
  var bytes = Uint8List.fromList(sdecoded);
  var p = ASN1Parser(bytes);
  var seq = p.nextObject() as ASN1Sequence;
  var ar = seq.elements?[0] as ASN1Integer;
  var as = seq.elements?[1] as ASN1Integer;

  var r = ar.integer!;
  var s = as.integer!;

  var ecSignature = ECSignature(r, s);

The Key is a SubjectPublicKeyInfo ASN1-Structure. Decoding this for an ECPublicKey is way more complicated. On the top-level it is an ASN1 Sequence consisting of an AlgorithmIdentifier (with its own ASN1-Substructure) and an ASN1 Bitstring , which contains a DER-encoded representation of the public key (with a structure depending on the AlgorithmIdentifier, as a RSA public key is very different from an EC public key). BouncyCastle has a decoder for this. You may want to look at the bc implementation and port it to dart but that is really a lot of work, even if one restrict it to decoding a SubjectPublicKeyInfo containing an ECPublicKey.

Or you may use the dart-packages x509 and crypto_keys like this:

import 'package:crypto_keys/crypto_keys.dart' as crypto;
import 'package:x509/x509.dart' as x509;

ECDomainParameters createCurveParameters(crypto.Identifier curve) {
  var name = curve.name.split('/').last;
  switch (name) {
    case 'P-256':
      return ECDomainParameters('secp256r1');
    case 'P-256K':
      return ECDomainParameters('secp256k1');
    case 'P-384':
      return ECDomainParameters('secp384r1');
    case 'P-521':
      return ECDomainParameters('secp521r1');
  }
  throw ArgumentError('Unknwon curve type $name');
}

ECPublicKey getPublicKey(String publicKeyInfo) {
  var keyInfo = x509.parsePem(publicKeyInfo).first as x509.SubjectPublicKeyInfo;
  var key = keyInfo.subjectPublicKey as crypto.EcPublicKey;
  var d = createCurveParameters(key.curve);

  var ecPublickey =
      ECPublicKey(d.curve.createPoint(key.xCoordinate, key.yCoordinate), d);
  return ecPublickey;
}

Unfortunately this limits you to the curves supported by x509 and crypto_keys, which are just the NIST-curves.

Once you have both the ECSignature and the ECPublicKey - and the data signed as an Uint8List of bytes, verification is as follows:

var verifier = Signer('SHA-256/ECDSA')..init(false, PublicKeyParameters(ecPublicKey));
var result = verifier.verify(data, ecSignature);

Instead of 'SHA-256/..' use the digest-designator for the digest algorithm used to sign.

tallinn1960 commented 2 years ago

For decoding the public key I found a less limiting helper package 'basic_utils' on pub.dev. It features a function

ECPublicKey ecPublicKeyFromPem(String pem);

and seems to support all curves of pointycastle.

KODA-Christian commented 2 years ago

First of all thanks to you @tallinn1960,

My biggest problem was with converting to ECSignature, ECPublicKey,...

Looks great, I'm checking that out and will give feedback very soon.

Again, I really appreciate your help.

Thanks and best wishes.

Ephenodrom commented 2 years ago

@KODA-Christian @tallinn1960 Converting a EC Signature to base64 and verifying a base64 EC signature is now available with the package basic_utils 4.3.0.

Take a look at the implementation :

  ///
  /// Verifying the given [signedData] with the given [publicKey] and the given [signature] in base64.
  /// Will return **true** if the given [signature] matches the [signedData].
  ///
  /// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are :
  ///
  /// * SHA-1/ECDSA
  /// * SHA-224/ECDSA
  /// * SHA-256/ECDSA
  /// * SHA-384/ECDSA
  /// * SHA-512/ECDSA
  /// * SHA-1/DET-ECDSA
  /// * SHA-224/DET-ECDSA
  /// * SHA-256/DET-ECDSA
  /// * SHA-384/DET-ECDSA
  /// * SHA-512/DET-ECDSA
  ///
  static bool ecVerifyBase64(
      ECPublicKey publicKey, Uint8List origData, String signature,
      {String algorithm = 'SHA-1/ECDSA'}) {
    var ecSignature = ecSignatureFromBase64(signature);

    return ecVerify(publicKey, origData, ecSignature, algorithm: algorithm);
  }

There is also an example on how to use the package for this case :

https://github.com/Ephenodrom/Dart-Basic-Utils/blob/master/example/elliptic_curve.dart