Move ECDSA_SIG out of BCM

This CL adjusts the libcrypto <-> BCM ECDSA interface. Previously, we
used ECDSA_do_sign and ECDSA_do_verify. This meant we have an allocated
BIGNUM-based type (ECDSA_SIG) at the boundary.

Instead use the fixed-width P1363 format at the boundary, which is nice
and straightforward. For now, I haven't exported it out of anything,
though we do have some things (Channel ID, WebCrypto) which actually
want this format, so that may be worth revisiting later.

Bug: 42290602
Change-Id: Ifbe0600fd23addc5f05141d18baad21a669ceca8
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66829
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
diff --git a/crypto/ecdsa_extra/ecdsa_asn1.c b/crypto/ecdsa_extra/ecdsa_asn1.c
index 8ddfb3b..b2d4fc6 100644
--- a/crypto/ecdsa_extra/ecdsa_asn1.c
+++ b/crypto/ecdsa_extra/ecdsa_asn1.c
@@ -62,34 +62,87 @@
 #include <openssl/mem.h>
 
 #include "../bytestring/internal.h"
-#include "../fipsmodule/ec/internal.h"
+#include "../fipsmodule/ecdsa/internal.h"
 #include "../internal.h"
 
 
+static ECDSA_SIG *ecdsa_sig_from_fixed(const EC_KEY *key, const uint8_t *in,
+                                       size_t len) {
+  const EC_GROUP *group = EC_KEY_get0_group(key);
+  if (group == NULL) {
+    OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
+    return NULL;
+  }
+  size_t scalar_len = BN_num_bytes(EC_GROUP_get0_order(group));
+  if (len != 2 * scalar_len) {
+    OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_BAD_SIGNATURE);
+    return NULL;
+  }
+  ECDSA_SIG *ret = ECDSA_SIG_new();
+  if (ret == NULL ||
+      !BN_bin2bn(in, scalar_len, ret->r) ||
+      !BN_bin2bn(in + scalar_len, scalar_len, ret->s)) {
+    ECDSA_SIG_free(ret);
+    return NULL;
+  }
+  return ret;
+}
+
+static int ecdsa_sig_to_fixed(const EC_KEY *key, uint8_t *out, size_t *out_len,
+                              size_t max_out, const ECDSA_SIG *sig) {
+  const EC_GROUP *group = EC_KEY_get0_group(key);
+  if (group == NULL) {
+    OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
+    return 0;
+  }
+  size_t scalar_len = BN_num_bytes(EC_GROUP_get0_order(group));
+  if (max_out < 2 * scalar_len) {
+    OPENSSL_PUT_ERROR(EC, EC_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+  if (BN_is_negative(sig->r) ||
+      !BN_bn2bin_padded(out, scalar_len, sig->r) ||
+      BN_is_negative(sig->s) ||
+      !BN_bn2bin_padded(out + scalar_len, scalar_len, sig->s)) {
+    OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_BAD_SIGNATURE);
+    return 0;
+  }
+  *out_len = 2 * scalar_len;
+  return 1;
+}
+
 int ECDSA_sign(int type, const uint8_t *digest, size_t digest_len, uint8_t *sig,
-               unsigned int *sig_len, const EC_KEY *eckey) {
+               unsigned int *out_sig_len, const EC_KEY *eckey) {
   if (eckey->ecdsa_meth && eckey->ecdsa_meth->sign) {
-    return eckey->ecdsa_meth->sign(digest, digest_len, sig, sig_len,
+    return eckey->ecdsa_meth->sign(digest, digest_len, sig, out_sig_len,
                                    (EC_KEY*) eckey /* cast away const */);
   }
 
-  int ret = 0;
-  ECDSA_SIG *s = ECDSA_do_sign(digest, digest_len, eckey);
-  if (s == NULL) {
-    *sig_len = 0;
-    goto err;
+  *out_sig_len = 0;
+  uint8_t fixed[ECDSA_MAX_FIXED_LEN];
+  size_t fixed_len;
+  if (!ecdsa_sign_fixed(digest, digest_len, fixed, &fixed_len, sizeof(fixed),
+                        eckey)) {
+    return 0;
   }
 
+  // TODO(davidben): We can actually do better and go straight from the DER
+  // format to the fixed-width format without a malloc.
+  ECDSA_SIG *s = ecdsa_sig_from_fixed(eckey, fixed, fixed_len);
+  if (s == NULL) {
+    return 0;
+  }
+
+  int ret = 0;
   CBB cbb;
   CBB_init_fixed(&cbb, sig, ECDSA_size(eckey));
   size_t len;
   if (!ECDSA_SIG_marshal(&cbb, s) ||
       !CBB_finish(&cbb, NULL, &len)) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_ENCODE_ERROR);
-    *sig_len = 0;
     goto err;
   }
-  *sig_len = (unsigned)len;
+  *out_sig_len = (unsigned)len;
   ret = 1;
 
 err:
@@ -99,12 +152,13 @@
 
 int ECDSA_verify(int type, const uint8_t *digest, size_t digest_len,
                  const uint8_t *sig, size_t sig_len, const EC_KEY *eckey) {
-  ECDSA_SIG *s;
+  // Decode the ECDSA signature.
+  //
+  // TODO(davidben): We can actually do better and go straight from the DER
+  // format to the fixed-width format without a malloc.
   int ret = 0;
   uint8_t *der = NULL;
-
-  // Decode the ECDSA signature.
-  s = ECDSA_SIG_from_bytes(sig, sig_len);
+  ECDSA_SIG *s = ECDSA_SIG_from_bytes(sig, sig_len);
   if (s == NULL) {
     goto err;
   }
@@ -118,7 +172,10 @@
     goto err;
   }
 
-  ret = ECDSA_do_verify(digest, digest_len, s, eckey);
+  uint8_t fixed[ECDSA_MAX_FIXED_LEN];
+  size_t fixed_len;
+  ret = ecdsa_sig_to_fixed(eckey, fixed, &fixed_len, sizeof(fixed), s) &&
+        ecdsa_verify_fixed(digest, digest_len, fixed, fixed_len, eckey);
 
 err:
   OPENSSL_free(der);
@@ -147,6 +204,95 @@
   return ECDSA_SIG_max_len(group_order_size);
 }
 
+ECDSA_SIG *ECDSA_SIG_new(void) {
+  ECDSA_SIG *sig = OPENSSL_malloc(sizeof(ECDSA_SIG));
+  if (sig == NULL) {
+    return NULL;
+  }
+  sig->r = BN_new();
+  sig->s = BN_new();
+  if (sig->r == NULL || sig->s == NULL) {
+    ECDSA_SIG_free(sig);
+    return NULL;
+  }
+  return sig;
+}
+
+void ECDSA_SIG_free(ECDSA_SIG *sig) {
+  if (sig == NULL) {
+    return;
+  }
+
+  BN_free(sig->r);
+  BN_free(sig->s);
+  OPENSSL_free(sig);
+}
+
+const BIGNUM *ECDSA_SIG_get0_r(const ECDSA_SIG *sig) {
+  return sig->r;
+}
+
+const BIGNUM *ECDSA_SIG_get0_s(const ECDSA_SIG *sig) {
+  return sig->s;
+}
+
+void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **out_r,
+                    const BIGNUM **out_s) {
+  if (out_r != NULL) {
+    *out_r = sig->r;
+  }
+  if (out_s != NULL) {
+    *out_s = sig->s;
+  }
+}
+
+int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
+  if (r == NULL || s == NULL) {
+    return 0;
+  }
+  BN_free(sig->r);
+  BN_free(sig->s);
+  sig->r = r;
+  sig->s = s;
+  return 1;
+}
+
+int ECDSA_do_verify(const uint8_t *digest, size_t digest_len,
+                    const ECDSA_SIG *sig, const EC_KEY *eckey) {
+  uint8_t fixed[ECDSA_MAX_FIXED_LEN];
+  size_t fixed_len;
+  return ecdsa_sig_to_fixed(eckey, fixed, &fixed_len, sizeof(fixed), sig) &&
+         ecdsa_verify_fixed(digest, digest_len, fixed, fixed_len, eckey);
+}
+
+// This function is only exported for testing and is not called in production
+// code.
+ECDSA_SIG *ECDSA_sign_with_nonce_and_leak_private_key_for_testing(
+    const uint8_t *digest, size_t digest_len, const EC_KEY *eckey,
+    const uint8_t *nonce, size_t nonce_len) {
+  uint8_t sig[ECDSA_MAX_FIXED_LEN];
+  size_t sig_len;
+  if (!ecdsa_sign_fixed_with_nonce_for_known_answer_test(
+          digest, digest_len, sig, &sig_len, sizeof(sig), eckey, nonce,
+          nonce_len)) {
+    return NULL;
+  }
+
+  return ecdsa_sig_from_fixed(eckey, sig, sig_len);
+}
+
+ECDSA_SIG *ECDSA_do_sign(const uint8_t *digest, size_t digest_len,
+                         const EC_KEY *eckey) {
+  uint8_t sig[ECDSA_MAX_FIXED_LEN];
+  size_t sig_len;
+  if (!ecdsa_sign_fixed(digest, digest_len, sig, &sig_len, sizeof(sig),
+                        eckey)) {
+    return NULL;
+  }
+
+  return ecdsa_sig_from_fixed(eckey, sig, sig_len);
+}
+
 ECDSA_SIG *ECDSA_SIG_parse(CBS *cbs) {
   ECDSA_SIG *ret = ECDSA_SIG_new();
   if (ret == NULL) {
diff --git a/crypto/fipsmodule/bcm.c b/crypto/fipsmodule/bcm.c
index c356730..48becb9 100644
--- a/crypto/fipsmodule/bcm.c
+++ b/crypto/fipsmodule/bcm.c
@@ -196,7 +196,7 @@
   assert_within(start, RAND_bytes, end);
   assert_within(start, EC_GROUP_cmp, end);
   assert_within(start, SHA256_Update, end);
-  assert_within(start, ECDSA_do_verify, end);
+  assert_within(start, ecdsa_verify_fixed, end);
   assert_within(start, EVP_AEAD_CTX_seal, end);
 
 #if defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/crypto/fipsmodule/ec/ec_key.c b/crypto/fipsmodule/ec/ec_key.c
index 8cdb5c3..1eef021 100644
--- a/crypto/fipsmodule/ec/ec_key.c
+++ b/crypto/fipsmodule/ec/ec_key.c
@@ -75,10 +75,12 @@
 #include <openssl/err.h>
 #include <openssl/ex_data.h>
 #include <openssl/mem.h>
+#include <openssl/sha.h>
 #include <openssl/thread.h>
 
 #include "internal.h"
 #include "../delocate.h"
+#include "../ecdsa/internal.h"
 #include "../service_indicator/internal.h"
 #include "../../internal.h"
 
@@ -344,15 +346,17 @@
   }
 
   if (key->priv_key) {
-    uint8_t data[16] = {0};
-    ECDSA_SIG *sig = ECDSA_do_sign(data, sizeof(data), key);
-    if (boringssl_fips_break_test("ECDSA_PWCT")) {
-      data[0] = ~data[0];
+    uint8_t digest[SHA256_DIGEST_LENGTH] = {0};
+    uint8_t sig[ECDSA_MAX_FIXED_LEN];
+    size_t sig_len;
+    if (!ecdsa_sign_fixed(digest, sizeof(digest), sig, &sig_len, sizeof(sig),
+                          key)) {
+      goto end;
     }
-    int ok = sig != NULL &&
-             ECDSA_do_verify(data, sizeof(data), sig, key);
-    ECDSA_SIG_free(sig);
-    if (!ok) {
+    if (boringssl_fips_break_test("ECDSA_PWCT")) {
+      digest[0] = ~digest[0];
+    }
+    if (!ecdsa_verify_fixed(digest, sizeof(digest), sig, sig_len, key)) {
       OPENSSL_PUT_ERROR(EC, EC_R_PUBLIC_KEY_VALIDATION_FAILED);
       goto end;
     }
diff --git a/crypto/fipsmodule/ecdsa/ecdsa.c b/crypto/fipsmodule/ecdsa/ecdsa.c
index 3bd018d..cba8972 100644
--- a/crypto/fipsmodule/ecdsa/ecdsa.c
+++ b/crypto/fipsmodule/ecdsa/ecdsa.c
@@ -95,61 +95,9 @@
                           order->width);
 }
 
-ECDSA_SIG *ECDSA_SIG_new(void) {
-  ECDSA_SIG *sig = OPENSSL_malloc(sizeof(ECDSA_SIG));
-  if (sig == NULL) {
-    return NULL;
-  }
-  sig->r = BN_new();
-  sig->s = BN_new();
-  if (sig->r == NULL || sig->s == NULL) {
-    ECDSA_SIG_free(sig);
-    return NULL;
-  }
-  return sig;
-}
-
-void ECDSA_SIG_free(ECDSA_SIG *sig) {
-  if (sig == NULL) {
-    return;
-  }
-
-  BN_free(sig->r);
-  BN_free(sig->s);
-  OPENSSL_free(sig);
-}
-
-const BIGNUM *ECDSA_SIG_get0_r(const ECDSA_SIG *sig) {
-  return sig->r;
-}
-
-const BIGNUM *ECDSA_SIG_get0_s(const ECDSA_SIG *sig) {
-  return sig->s;
-}
-
-void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **out_r,
-                    const BIGNUM **out_s) {
-  if (out_r != NULL) {
-    *out_r = sig->r;
-  }
-  if (out_s != NULL) {
-    *out_s = sig->s;
-  }
-}
-
-int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
-  if (r == NULL || s == NULL) {
-    return 0;
-  }
-  BN_free(sig->r);
-  BN_free(sig->s);
-  sig->r = r;
-  sig->s = s;
-  return 1;
-}
-
-int ecdsa_do_verify_no_self_test(const uint8_t *digest, size_t digest_len,
-                                 const ECDSA_SIG *sig, const EC_KEY *eckey) {
+int ecdsa_verify_fixed_no_self_test(const uint8_t *digest, size_t digest_len,
+                                    const uint8_t *sig, size_t sig_len,
+                                    const EC_KEY *eckey) {
   const EC_GROUP *group = EC_KEY_get0_group(eckey);
   const EC_POINT *pub_key = EC_KEY_get0_public_key(eckey);
   if (group == NULL || pub_key == NULL || sig == NULL) {
@@ -157,11 +105,13 @@
     return 0;
   }
 
+  size_t scalar_len = BN_num_bytes(EC_GROUP_get0_order(group));
   EC_SCALAR r, s, u1, u2, s_inv_mont, m;
-  if (BN_is_zero(sig->r) ||
-      !ec_bignum_to_scalar(group, &r, sig->r) ||
-      BN_is_zero(sig->s) ||
-      !ec_bignum_to_scalar(group, &s, sig->s)) {
+  if (sig_len != 2 * scalar_len ||
+      !ec_scalar_from_bytes(group, &r, sig, scalar_len) ||
+      ec_scalar_is_zero(group, &r) ||
+      !ec_scalar_from_bytes(group, &s, sig + scalar_len, scalar_len) ||
+      ec_scalar_is_zero(group, &s)) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_BAD_SIGNATURE);
     return 0;
   }
@@ -195,24 +145,31 @@
   return 1;
 }
 
-int ECDSA_do_verify(const uint8_t *digest, size_t digest_len,
-                    const ECDSA_SIG *sig, const EC_KEY *eckey) {
+int ecdsa_verify_fixed(const uint8_t *digest, size_t digest_len,
+                       const uint8_t *sig, size_t sig_len, const EC_KEY *key) {
   boringssl_ensure_ecc_self_test();
 
-  return ecdsa_do_verify_no_self_test(digest, digest_len, sig, eckey);
+  return ecdsa_verify_fixed_no_self_test(digest, digest_len, sig, sig_len, key);
 }
 
-static ECDSA_SIG *ecdsa_sign_impl(const EC_GROUP *group, int *out_retry,
-                                  const EC_SCALAR *priv_key, const EC_SCALAR *k,
-                                  const uint8_t *digest, size_t digest_len) {
+static int ecdsa_sign_impl(const EC_GROUP *group, int *out_retry, uint8_t *sig,
+                           size_t *out_sig_len, size_t max_sig_len,
+                           const EC_SCALAR *priv_key, const EC_SCALAR *k,
+                           const uint8_t *digest, size_t digest_len) {
   *out_retry = 0;
 
   // Check that the size of the group order is FIPS compliant (FIPS 186-4
   // B.5.2).
   const BIGNUM *order = EC_GROUP_get0_order(group);
   if (BN_num_bits(order) < 160) {
-    OPENSSL_PUT_ERROR(ECDSA, EC_R_INVALID_GROUP_ORDER);
-    return NULL;
+    OPENSSL_PUT_ERROR(EC, EC_R_INVALID_GROUP_ORDER);
+    return 0;
+  }
+
+  size_t sig_len = 2 * BN_num_bytes(order);
+  if (sig_len > max_sig_len) {
+    OPENSSL_PUT_ERROR(EC, EC_R_BUFFER_TOO_SMALL);
+    return 0;
   }
 
   // Compute r, the x-coordinate of k * generator.
@@ -220,12 +177,12 @@
   EC_SCALAR r;
   if (!ec_point_mul_scalar_base(group, &tmp_point, k) ||
       !ec_get_x_coordinate_as_scalar(group, &r, &tmp_point)) {
-    return NULL;
+    return 0;
   }
 
   if (constant_time_declassify_int(ec_scalar_is_zero(group, &r))) {
     *out_retry = 1;
-    return NULL;
+    return 0;
   }
 
   // s = priv_key * r. Note if only one parameter is in the Montgomery domain,
@@ -252,71 +209,59 @@
   ec_scalar_mul_montgomery(group, &s, &s, &tmp);
   if (constant_time_declassify_int(ec_scalar_is_zero(group, &s))) {
     *out_retry = 1;
-    return NULL;
+    return 0;
   }
 
   CONSTTIME_DECLASSIFY(r.words, sizeof(r.words));
   CONSTTIME_DECLASSIFY(s.words, sizeof(r.words));
-  ECDSA_SIG *ret = ECDSA_SIG_new();
-  if (ret == NULL ||  //
-      !bn_set_words(ret->r, r.words, order->width) ||
-      !bn_set_words(ret->s, s.words, order->width)) {
-    ECDSA_SIG_free(ret);
-    return NULL;
-  }
-  return ret;
+  size_t len;
+  ec_scalar_to_bytes(group, sig, &len, &r);
+  assert(len == sig_len / 2);
+  ec_scalar_to_bytes(group, sig + len, &len, &s);
+  assert(len == sig_len / 2);
+  *out_sig_len = sig_len;
+  return 1;
 }
 
-ECDSA_SIG *ecdsa_sign_with_nonce_for_known_answer_test(const uint8_t *digest,
-                                                       size_t digest_len,
-                                                       const EC_KEY *eckey,
-                                                       const uint8_t *nonce,
-                                                       size_t nonce_len) {
+int ecdsa_sign_fixed_with_nonce_for_known_answer_test(
+    const uint8_t *digest, size_t digest_len, uint8_t *sig, size_t *out_sig_len,
+    size_t max_sig_len, const EC_KEY *eckey, const uint8_t *nonce,
+    size_t nonce_len) {
   if (eckey->ecdsa_meth && eckey->ecdsa_meth->sign) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NOT_IMPLEMENTED);
-    return NULL;
+    return 0;
   }
 
   const EC_GROUP *group = EC_KEY_get0_group(eckey);
   if (group == NULL || eckey->priv_key == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
-    return NULL;
+    return 0;
   }
   const EC_SCALAR *priv_key = &eckey->priv_key->scalar;
 
   EC_SCALAR k;
   if (!ec_scalar_from_bytes(group, &k, nonce, nonce_len)) {
-    return NULL;
+    return 0;
   }
   int retry_ignored;
-  return ecdsa_sign_impl(group, &retry_ignored, priv_key, &k, digest,
-                         digest_len);
+  return ecdsa_sign_impl(group, &retry_ignored, sig, out_sig_len, max_sig_len,
+                         priv_key, &k, digest, digest_len);
 }
 
-// This function is only exported for testing and is not called in production
-// code.
-ECDSA_SIG *ECDSA_sign_with_nonce_and_leak_private_key_for_testing(
-    const uint8_t *digest, size_t digest_len, const EC_KEY *eckey,
-    const uint8_t *nonce, size_t nonce_len) {
-  boringssl_ensure_ecc_self_test();
-
-  return ecdsa_sign_with_nonce_for_known_answer_test(digest, digest_len, eckey,
-                                                     nonce, nonce_len);
-}
-
-ECDSA_SIG *ECDSA_do_sign(const uint8_t *digest, size_t digest_len,
-                         const EC_KEY *eckey) {
+int ecdsa_sign_fixed(const uint8_t *digest, size_t digest_len, uint8_t *sig,
+                     size_t *out_sig_len, size_t max_sig_len,
+                     const EC_KEY *eckey) {
   boringssl_ensure_ecc_self_test();
 
   if (eckey->ecdsa_meth && eckey->ecdsa_meth->sign) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NOT_IMPLEMENTED);
-    return NULL;
+    return 0;
   }
 
   const EC_GROUP *group = EC_KEY_get0_group(eckey);
   if (group == NULL || eckey->priv_key == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
-    return NULL;
+    return 0;
   }
   const BIGNUM *order = EC_GROUP_get0_order(group);
   const EC_SCALAR *priv_key = &eckey->priv_key->scalar;
@@ -340,12 +285,11 @@
   // FIPS) because the probability of requiring even one retry is negligible,
   // let alone 32.
   static const int kMaxIterations = 32;
-  ECDSA_SIG *ret = NULL;
+  int ret = 0;
   int iters = 0;
   for (;;) {
     EC_SCALAR k;
     if (!ec_random_nonzero_scalar(group, &k, additional_data)) {
-      ret = NULL;
       goto out;
     }
 
@@ -354,8 +298,9 @@
     CONSTTIME_SECRET(k.words, sizeof(k.words));
 
     int retry;
-    ret = ecdsa_sign_impl(group, &retry, priv_key, &k, digest, digest_len);
-    if (ret != NULL || !retry) {
+    ret = ecdsa_sign_impl(group, &retry, sig, out_sig_len, max_sig_len,
+                          priv_key, &k, digest, digest_len);
+    if (ret || !retry) {
       goto out;
     }
 
diff --git a/crypto/fipsmodule/ecdsa/ecdsa_test.cc b/crypto/fipsmodule/ecdsa/ecdsa_test.cc
index 5876935..1856210 100644
--- a/crypto/fipsmodule/ecdsa/ecdsa_test.cc
+++ b/crypto/fipsmodule/ecdsa/ecdsa_test.cc
@@ -285,6 +285,14 @@
 
     // Verify a tampered signature.
     TestTamperedSig(kRawAPI, digest, 20, ecdsa_sig.get(), eckey.get(), order);
+
+    // Negative components should not be accepted.
+    BN_set_negative(ecdsa_sig->r, 1);
+    EXPECT_FALSE(ECDSA_do_verify(digest, 20, ecdsa_sig.get(), eckey.get()));
+    BN_set_negative(ecdsa_sig->r, 0);
+    BN_set_negative(ecdsa_sig->s, 1);
+    EXPECT_FALSE(ECDSA_do_verify(digest, 20, ecdsa_sig.get(), eckey.get()));
+    BN_set_negative(ecdsa_sig->s, 0);
   }
 }
 
diff --git a/crypto/fipsmodule/ecdsa/internal.h b/crypto/fipsmodule/ecdsa/internal.h
index 645959f..0ae08ac 100644
--- a/crypto/fipsmodule/ecdsa/internal.h
+++ b/crypto/fipsmodule/ecdsa/internal.h
@@ -17,25 +17,42 @@
 
 #include <openssl/base.h>
 
+#include "../ec/internal.h"
+
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 
-// ecdsa_sign_with_nonce_for_known_answer_test behaves like |ECDSA_do_sign| but
-// takes a fixed nonce. This function is used as part of known-answer tests in
-// the FIPS module.
-ECDSA_SIG *ecdsa_sign_with_nonce_for_known_answer_test(const uint8_t *digest,
-                                                       size_t digest_len,
-                                                       const EC_KEY *eckey,
-                                                       const uint8_t *nonce,
-                                                       size_t nonce_len);
+// ECDSA_MAX_FIXED_LEN is the maximum length of an ECDSA signature in the
+// fixed-width, big-endian format from IEEE P1363.
+#define ECDSA_MAX_FIXED_LEN (2 * EC_MAX_BYTES)
 
-// ecdsa_do_verify_no_self_test does the same as |ECDSA_do_verify|, but doesn't
+// ecdsa_sign_fixed behaves like |ECDSA_sign| but uses the fixed-width,
+// big-endian format from IEEE P1363.
+int ecdsa_sign_fixed(const uint8_t *digest, size_t digest_len, uint8_t *sig,
+                     size_t *out_sig_len, size_t max_sig_len,
+                     const EC_KEY *key);
+
+// ecdsa_sign_fixed_with_nonce_for_known_answer_test behaves like
+// |ecdsa_sign_fixed| but takes a caller-supplied nonce. This function is used
+// as part of known-answer tests in the FIPS module.
+int ecdsa_sign_fixed_with_nonce_for_known_answer_test(
+    const uint8_t *digest, size_t digest_len, uint8_t *sig, size_t *out_sig_len,
+    size_t max_sig_len, const EC_KEY *key, const uint8_t *nonce,
+    size_t nonce_len);
+
+// ecdsa_verify_fixed behaves like |ECDSA_verify| but uses the fixed-width,
+// big-endian format from IEEE P1363.
+int ecdsa_verify_fixed(const uint8_t *digest, size_t digest_len,
+                       const uint8_t *sig, size_t sig_len, const EC_KEY *key);
+
+// ecdsa_verify_fixed_no_self_test behaves like ecdsa_verify_fixed, but doesn't
 // try to run the self-test first. This is for use in the self tests themselves,
 // to prevent an infinite loop.
-int ecdsa_do_verify_no_self_test(const uint8_t *digest, size_t digest_len,
-                                 const ECDSA_SIG *sig, const EC_KEY *eckey);
+int ecdsa_verify_fixed_no_self_test(const uint8_t *digest, size_t digest_len,
+                                    const uint8_t *sig, size_t sig_len,
+                                    const EC_KEY *key);
 
 
 #if defined(__cplusplus)
diff --git a/crypto/fipsmodule/self_check/self_check.c b/crypto/fipsmodule/self_check/self_check.c
index 1984630..7afcb75 100644
--- a/crypto/fipsmodule/self_check/self_check.c
+++ b/crypto/fipsmodule/self_check/self_check.c
@@ -77,28 +77,6 @@
   return *out != NULL;
 }
 
-static int serialize_ecdsa_sig(uint8_t *out, size_t out_len,
-                               const ECDSA_SIG *sig) {
-  if ((out_len & 1) ||  //
-      !BN_bn2bin_padded(out, out_len / 2, sig->r) ||
-      !BN_bn2bin_padded(out + out_len / 2, out_len / 2, sig->s)) {
-    return 0;
-  }
-  return 1;
-}
-
-static ECDSA_SIG *parse_ecdsa_sig(const uint8_t *in, size_t in_len) {
-  ECDSA_SIG *ret = ECDSA_SIG_new();
-  if (!ret || //
-      (in_len & 1) ||
-      BN_bin2bn(in, in_len/2, ret->r) == NULL ||
-      BN_bin2bn(in + in_len/2, in_len/2, ret->s) == NULL) {
-    ECDSA_SIG_free(ret);
-    ret = NULL;
-  }
-  return ret;
-}
-
 static RSA *self_test_rsa_key(void) {
   static const uint8_t kN[] = {
       0xd3, 0x3a, 0x62, 0x9f, 0x07, 0x77, 0xb0, 0x18, 0xf3, 0xff, 0xfe, 0xcc,
@@ -415,7 +393,6 @@
   EC_POINT *ec_point_in = NULL;
   EC_POINT *ec_point_out = NULL;
   BIGNUM *ec_scalar = NULL;
-  ECDSA_SIG *sig = NULL;
 
   ec_key = self_test_ecdsa_key();
   if (ec_key == NULL) {
@@ -443,13 +420,12 @@
   uint8_t ecdsa_k[32] = {0};
   ecdsa_k[31] = 42;
 
-  sig = ecdsa_sign_with_nonce_for_known_answer_test(
-      kECDSASignDigest, sizeof(kECDSASignDigest), ec_key, ecdsa_k,
-      sizeof(ecdsa_k));
-
   uint8_t ecdsa_sign_output[64];
-  if (sig == NULL ||
-      !serialize_ecdsa_sig(ecdsa_sign_output, sizeof(ecdsa_sign_output), sig) ||
+  size_t ecdsa_sign_output_len;
+  if (!ecdsa_sign_fixed_with_nonce_for_known_answer_test(
+          kECDSASignDigest, sizeof(kECDSASignDigest), ecdsa_sign_output,
+          &ecdsa_sign_output_len, sizeof(ecdsa_sign_output), ec_key, ecdsa_k,
+          sizeof(ecdsa_k)) ||
       !check_test(kECDSASignSig, ecdsa_sign_output, sizeof(ecdsa_sign_output),
                   "ECDSA-sign signature")) {
     fprintf(stderr, "ECDSA-sign KAT failed.\n");
@@ -470,11 +446,9 @@
       0x8e, 0x5f, 0x64, 0xc3, 0x7e, 0xa2, 0xcf, 0x05, 0x29,
   };
 
-  ECDSA_SIG_free(sig);
-  sig = parse_ecdsa_sig(kECDSAVerifySig, sizeof(kECDSAVerifySig));
-  if (!sig ||
-      !ecdsa_do_verify_no_self_test(kECDSAVerifyDigest,
-                                    sizeof(kECDSAVerifyDigest), sig, ec_key)) {
+  if (!ecdsa_verify_fixed_no_self_test(
+          kECDSAVerifyDigest, sizeof(kECDSAVerifyDigest), kECDSAVerifySig,
+          sizeof(kECDSAVerifySig), ec_key)) {
     fprintf(stderr, "ECDSA-verify KAT failed.\n");
     goto err;
   }
@@ -532,7 +506,6 @@
   EC_POINT_free(ec_point_in);
   EC_POINT_free(ec_point_out);
   BN_free(ec_scalar);
-  ECDSA_SIG_free(sig);
 
   return ret;
 }