Fix error-handling convention in x509_vfy.c and avoid -1 returns

This CL makes two changes. First, it removes the couple of places where
X509_verify_cert may return -1 and switches to our standard 0/1 return
convention. The only -1 cases were get_issuer returning < 0 and the
caller error cases at the top. It seems implausible that any caller
would care about the latter and the former is actually impossible.
get_issuer never returns < 0.

Second, OpenSSL's original implementation did not follow the usual
error-handling convention. The usual convention is that there's a
cleanup epilog, and a variable (usually called 'ret' or 'ok') that
stores the return value. This variable is initialized in the failure
case and may only be modified immediately before a goto or when falling
through to the epilog. This allows error conditions to simply 'goto err'
and rely on the variable's value.

X509_verify_cert instead overwrite 'ok' throughout the function, which
is tedious and error-prone. Fix this to follow the usual convention.
Also remove uses of this pattern when there isn't anything to cleanup.

As part of this cleanup, we fix a near miss: the three cert_self_signed
call sites did not correctly account for this non-standard pattern.
Fortunately (as demonstrated by existing unit tests), the first call
site is fine. The remainder are only called on "trusted" certificates
from the X509_STORE. An attacker with control over trust anchors already
controls certificate verification, so this is moot. Moreover, all such
certificates first go through get_issuer, which calls X509_check_issued,
which already handles EXFLAG_INVALID, so the error condition was
redundant.

Update-Note: X509_verify_cert no longer returns -1 on some error
conditions, only zero.

Change-Id: I88d5e845cd4cb8f48d5c5df6782bf6730c682642
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65067
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index 35fa3e1..f64cfeb 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -182,7 +182,7 @@
   if (ctx->cert == NULL) {
     OPENSSL_PUT_ERROR(X509, X509_R_NO_CERT_SET_FOR_US_TO_VERIFY);
     ctx->error = X509_V_ERR_INVALID_CALL;
-    return -1;
+    return 0;
   }
 
   if (ctx->chain != NULL) {
@@ -190,7 +190,7 @@
     // cannot do another one.
     OPENSSL_PUT_ERROR(X509, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     ctx->error = X509_V_ERR_INVALID_CALL;
-    return -1;
+    return 0;
   }
 
   if (ctx->param->flags &
@@ -200,7 +200,7 @@
     // inadvertently skip a CRL check that the caller expects, fail closed.
     OPENSSL_PUT_ERROR(X509, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     ctx->error = X509_V_ERR_INVALID_CALL;
-    return -1;
+    return 0;
   }
 
   // first we make sure the chain we are going to build is present and that
@@ -258,7 +258,6 @@
       if (issuer != NULL) {
         if (!sk_X509_push(ctx->chain, issuer)) {
           ctx->error = X509_V_ERR_OUT_OF_MEM;
-          ok = 0;
           goto end;
         }
         X509_up_ref(issuer);
@@ -303,8 +302,7 @@
           ctx->current_cert = x;
           ctx->error_depth = i - 1;
           bad_chain = 1;
-          ok = call_verify_cb(0, ctx);
-          if (!ok) {
+          if (!call_verify_cb(0, ctx)) {
             goto end;
           }
         } else {
@@ -347,7 +345,6 @@
       if (!sk_X509_push(ctx->chain, x)) {
         X509_free(issuer);
         ctx->error = X509_V_ERR_OUT_OF_MEM;
-        ok = 0;
         goto end;
       }
       num++;
@@ -358,7 +355,6 @@
 
     // If explicitly rejected error
     if (trust == X509_TRUST_REJECTED) {
-      ok = 0;
       goto end;
     }
     // If it's not explicitly trusted then check if there is an alternative
@@ -414,59 +410,33 @@
 
     ctx->error_depth = num - 1;
     bad_chain = 1;
-    ok = call_verify_cb(0, ctx);
-    if (!ok) {
+    if (!call_verify_cb(0, ctx)) {
       goto end;
     }
   }
 
   // We have the chain complete: now we need to check its purpose
-  ok = check_chain_extensions(ctx);
-
-  if (!ok) {
+  if (!check_chain_extensions(ctx) ||  //
+      !check_id(ctx) ||
+      // We check revocation status after copying parameters because they may be
+      // needed for CRL signature verification.
+      !check_revocation(ctx) ||  //
+      !internal_verify(ctx) ||   //
+      !check_name_constraints(ctx) ||
+      // TODO(davidben): Does |check_policy| still need to be conditioned on
+      // |!bad_chain|? DoS concerns have been resolved.
+      (!bad_chain && !check_policy(ctx))) {
     goto end;
   }
 
-  ok = check_id(ctx);
-
-  if (!ok) {
-    goto end;
-  }
-
-  // Check revocation status: we do this after copying parameters because
-  // they may be needed for CRL signature verification.
-  ok = check_revocation(ctx);
-  if (!ok) {
-    goto end;
-  }
-
-  // At this point, we have a chain and need to verify it
-  ok = internal_verify(ctx);
-  if (!ok) {
-    goto end;
-  }
-
-  // Check name constraints
-  ok = check_name_constraints(ctx);
-  if (!ok) {
-    goto end;
-  }
-
-  // If we get this far, evaluate policies.
-  if (!bad_chain) {
-    ok = check_policy(ctx);
-  }
+  ok = 1;
 
 end:
-  if (sktmp != NULL) {
-    sk_X509_free(sktmp);
-  }
-  if (chain_ss != NULL) {
-    X509_free(chain_ss);
-  }
+  sk_X509_free(sktmp);
+  X509_free(chain_ss);
 
   // Safety net, error returns must set ctx->error
-  if (ok <= 0 && ctx->error == X509_V_OK) {
+  if (!ok && ctx->error == X509_V_OK) {
     ctx->error = X509_V_ERR_UNSPECIFIED;
   }
   return ok;
@@ -526,7 +496,7 @@
 // purpose
 
 static int check_chain_extensions(X509_STORE_CTX *ctx) {
-  int ok = 0, plen = 0;
+  int plen = 0;
   int purpose = ctx->param->purpose;
 
   // Check all untrusted certificates
@@ -537,9 +507,8 @@
       ctx->error = X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION;
       ctx->error_depth = i;
       ctx->current_cert = x;
-      ok = call_verify_cb(0, ctx);
-      if (!ok) {
-        goto end;
+      if (!call_verify_cb(0, ctx)) {
+        return 0;
       }
     }
 
@@ -548,9 +517,8 @@
       ctx->error = X509_V_ERR_INVALID_CA;
       ctx->error_depth = i;
       ctx->current_cert = x;
-      ok = call_verify_cb(0, ctx);
-      if (!ok) {
-        goto end;
+      if (!call_verify_cb(0, ctx)) {
+        return 0;
       }
     }
     if (ctx->param->purpose > 0 &&
@@ -558,9 +526,8 @@
       ctx->error = X509_V_ERR_INVALID_PURPOSE;
       ctx->error_depth = i;
       ctx->current_cert = x;
-      ok = call_verify_cb(0, ctx);
-      if (!ok) {
-        goto end;
+      if (!call_verify_cb(0, ctx)) {
+        return 0;
       }
     }
     // Check pathlen if not self issued
@@ -569,9 +536,8 @@
       ctx->error = X509_V_ERR_PATH_LENGTH_EXCEEDED;
       ctx->error_depth = i;
       ctx->current_cert = x;
-      ok = call_verify_cb(0, ctx);
-      if (!ok) {
-        goto end;
+      if (!call_verify_cb(0, ctx)) {
+        return 0;
       }
     }
     // Increment path length if not self issued
@@ -579,9 +545,8 @@
       plen++;
     }
   }
-  ok = 1;
-end:
-  return ok;
+
+  return 1;
 }
 
 static int reject_dns_name_in_common_name(X509 *x509) {
@@ -729,24 +694,22 @@
 }
 
 static int check_trust(X509_STORE_CTX *ctx) {
-  int ok;
   X509 *x = NULL;
   // Check all trusted certificates in chain
   for (size_t i = ctx->last_untrusted; i < sk_X509_num(ctx->chain); i++) {
     x = sk_X509_value(ctx->chain, i);
-    ok = X509_check_trust(x, ctx->param->trust, 0);
+    int trust = X509_check_trust(x, ctx->param->trust, 0);
     // If explicitly trusted return trusted
-    if (ok == X509_TRUST_TRUSTED) {
+    if (trust == X509_TRUST_TRUSTED) {
       return X509_TRUST_TRUSTED;
     }
     // If explicitly rejected notify callback and reject if not
     // overridden.
-    if (ok == X509_TRUST_REJECTED) {
+    if (trust == X509_TRUST_REJECTED) {
       ctx->error_depth = (int)i;
       ctx->current_cert = x;
       ctx->error = X509_V_ERR_CERT_REJECTED;
-      ok = call_verify_cb(0, ctx);
-      if (!ok) {
+      if (!call_verify_cb(0, ctx)) {
         return X509_TRUST_REJECTED;
       }
     }
@@ -785,9 +748,8 @@
   }
   for (int i = 0; i <= last; i++) {
     ctx->error_depth = i;
-    int ok = check_cert(ctx);
-    if (!ok) {
-      return ok;
+    if (!check_cert(ctx)) {
+      return 0;
     }
   }
   return 1;
@@ -807,23 +769,19 @@
   // TODO(davidben): Remove these callbacks. gRPC currently sets them, but
   // implements them incorrectly. It is not actually possible to implement
   // |get_crl| from outside the library.
-  ok = ctx->get_crl(ctx, &crl, x);
-  // If error looking up CRL, nothing we can do except notify callback
-  if (!ok) {
+  if (!ctx->get_crl(ctx, &crl, x)) {
     ctx->error = X509_V_ERR_UNABLE_TO_GET_CRL;
     ok = call_verify_cb(0, ctx);
     goto err;
   }
+
   ctx->current_crl = crl;
-  ok = ctx->check_crl(ctx, crl);
-  if (!ok) {
+  if (!ctx->check_crl(ctx, crl) ||  //
+      !cert_crl(ctx, crl, x)) {
     goto err;
   }
 
-  ok = cert_crl(ctx, crl, x);
-  if (!ok) {
-    goto err;
-  }
+  ok = 1;
 
 err:
   X509_CRL_free(crl);
@@ -1133,19 +1091,16 @@
 
 // Retrieve CRL corresponding to current certificate.
 static int get_crl(X509_STORE_CTX *ctx, X509_CRL **pcrl, X509 *x) {
-  int ok;
   X509 *issuer = NULL;
   int crl_score = 0;
   X509_CRL *crl = NULL;
-  STACK_OF(X509_CRL) *skcrl;
-  X509_NAME *nm = X509_get_issuer_name(x);
-  ok = get_crl_sk(ctx, &crl, &issuer, &crl_score, ctx->crls);
-  if (ok) {
+  if (get_crl_sk(ctx, &crl, &issuer, &crl_score, ctx->crls)) {
     goto done;
   }
 
   // Lookup CRLs from store
-  skcrl = X509_STORE_CTX_get1_crls(ctx, nm);
+  STACK_OF(X509_CRL) *skcrl =
+      X509_STORE_CTX_get1_crls(ctx, X509_get_issuer_name(x));
 
   // If no CRLs found and a near match from get_crl_sk use that
   if (!skcrl && crl) {
@@ -1244,8 +1199,6 @@
 
 // Check certificate against CRL
 static int cert_crl(X509_STORE_CTX *ctx, X509_CRL *crl, X509 *x) {
-  int ok;
-  X509_REVOKED *rev;
   // The rules changed for this... previously if a CRL contained unhandled
   // critical extensions it could still be used to indicate a certificate
   // was revoked. This has since been changed since critical extension can
@@ -1253,16 +1206,15 @@
   if (!(ctx->param->flags & X509_V_FLAG_IGNORE_CRITICAL) &&
       (crl->flags & EXFLAG_CRITICAL)) {
     ctx->error = X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION;
-    ok = call_verify_cb(0, ctx);
-    if (!ok) {
+    if (!call_verify_cb(0, ctx)) {
       return 0;
     }
   }
   // Look for serial number of certificate in CRL.
+  X509_REVOKED *rev;
   if (X509_CRL_get0_by_cert(crl, &rev, x)) {
     ctx->error = X509_V_ERR_CERT_REVOKED;
-    ok = call_verify_cb(0, ctx);
-    if (!ok) {
+    if (!call_verify_cb(0, ctx)) {
       return 0;
     }
   }
@@ -1336,14 +1288,19 @@
 }
 
 static int internal_verify(X509_STORE_CTX *ctx) {
-  int ok = 0;
-  X509 *xs, *xi;
-
+  // TODO(davidben): This logic is incredibly confusing. Rewrite this:
+  //
+  // First, don't allow the verify callback to suppress
+  // X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, which will simplify the
+  // signature check. Then replace jumping into the middle of the loop. It's
+  // trying to ensure that all certificates see |check_cert_time|, then checking
+  // the root's self signature when requested, but not breaking partial chains
+  // in the process.
   int n = (int)sk_X509_num(ctx->chain);
   ctx->error_depth = n - 1;
   n--;
-  xi = sk_X509_value(ctx->chain, n);
-
+  X509 *xi = sk_X509_value(ctx->chain, n);
+  X509 *xs;
   if (x509_check_issued_with_callback(ctx, xi, xi)) {
     xs = xi;
   } else {
@@ -1354,13 +1311,11 @@
     if (n <= 0) {
       ctx->error = X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
       ctx->current_cert = xi;
-      ok = call_verify_cb(0, ctx);
-      goto end;
-    } else {
-      n--;
-      ctx->error_depth = n;
-      xs = sk_X509_value(ctx->chain, n);
+      return call_verify_cb(0, ctx);
     }
+    n--;
+    ctx->error_depth = n;
+    xs = sk_X509_value(ctx->chain, n);
   }
 
   //      ctx->error=0;  not needed
@@ -1375,31 +1330,27 @@
       if (pkey == NULL) {
         ctx->error = X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
         ctx->current_cert = xi;
-        ok = call_verify_cb(0, ctx);
-        if (!ok) {
-          goto end;
+        if (!call_verify_cb(0, ctx)) {
+          return 0;
         }
       } else if (X509_verify(xs, pkey) <= 0) {
         ctx->error = X509_V_ERR_CERT_SIGNATURE_FAILURE;
         ctx->current_cert = xs;
-        ok = call_verify_cb(0, ctx);
-        if (!ok) {
-          goto end;
+        if (!call_verify_cb(0, ctx)) {
+          return 0;
         }
       }
     }
 
   check_cert:
-    ok = check_cert_time(ctx, xs);
-    if (!ok) {
-      goto end;
+    if (!check_cert_time(ctx, xs)) {
+      return 0;
     }
 
     // The last error (if any) is still in the error value
     ctx->current_cert = xs;
-    ok = call_verify_cb(1, ctx);
-    if (!ok) {
-      goto end;
+    if (!call_verify_cb(1, ctx)) {
+      return 0;
     }
 
     n--;
@@ -1408,9 +1359,8 @@
       xs = sk_X509_value(ctx->chain, n);
     }
   }
-  ok = 1;
-end:
-  return ok;
+
+  return 1;
 }
 
 int X509_cmp_current_time(const ASN1_TIME *ctm) {
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index d3f4493..0f5a222 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -3517,6 +3517,12 @@
 
 OPENSSL_EXPORT int X509_CRL_match(const X509_CRL *a, const X509_CRL *b);
 
+// X509_verify_cert performs certifice verification with |ctx|, which must have
+// been initialized with |X509_STORE_CTX_init|. It returns one on success and
+// zero on error. On success, |X509_STORE_CTX_get0_chain| or
+// |X509_STORE_CTX_get1_chain| may be used to return the verified certificate
+// chain. On error, |X509_STORE_CTX_get_error| may be used to return additional
+// error information.
 OPENSSL_EXPORT int X509_verify_cert(X509_STORE_CTX *ctx);
 
 OPENSSL_EXPORT int X509_check_trust(X509 *x, int id, int flags);
@@ -3863,14 +3869,52 @@
 OPENSSL_EXPORT int X509_STORE_load_locations(X509_STORE *ctx, const char *file,
                                              const char *dir);
 OPENSSL_EXPORT int X509_STORE_set_default_paths(X509_STORE *ctx);
+
+// X509_STORE_CTX_get_error, after |X509_verify_cert| returns, returns
+// |X509_V_OK| if verification succeeded or an |X509_V_ERR_*| describing why
+// verification failed. This will be consistent with |X509_verify_cert|'s return
+// value, unless the caller used the deprecated verification callback (see
+// |X509_STORE_CTX_set_verify_cb|) in a way that breaks |ctx|'s invariants.
+//
+// If called during the deprecated verification callback when |ok| is zero, it
+// returns the current error under consideration.
 OPENSSL_EXPORT int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx);
-OPENSSL_EXPORT void X509_STORE_CTX_set_error(X509_STORE_CTX *ctx, int s);
+
+// X509_STORE_CTX_set_error sets |ctx|'s error to |err|, which should be
+// |X509_V_OK| or an |X509_V_ERR_*| constant. It is not expected to be called in
+// typical |X509_STORE_CTX| usage, but may be used in callback APIs where
+// applications synthesize |X509_STORE_CTX| error conditions. See also
+// |X509_STORE_CTX_set_verify_cb| and |SSL_CTX_set_cert_verify_callback|.
+OPENSSL_EXPORT void X509_STORE_CTX_set_error(X509_STORE_CTX *ctx, int err);
+
+// X509_STORE_CTX_get_error_depth returns the depth at which the error returned
+// by |X509_STORE_CTX_get_error| occured. This is zero-indexed integer into the
+// certificate chain. Zero indicates the target certificate, one its issuer, and
+// so on.
 OPENSSL_EXPORT int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx);
+
 OPENSSL_EXPORT X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx);
 OPENSSL_EXPORT X509_CRL *X509_STORE_CTX_get0_current_crl(X509_STORE_CTX *ctx);
+
+// X509_STORE_CTX_get_chain is a legacy alias for |X509_STORE_CTX_get0_chain|.
 OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get_chain(X509_STORE_CTX *ctx);
+
+// X509_STORE_CTX_get0_chain, after a successful |X509_verify_cert| call,
+// returns the verified certificate chain. The chain begins with the leaf and
+// ends with trust anchor.
+//
+// At other points, such as after a failed verification or during the deprecated
+// verification callback, it returns the partial chain built so far. Callers
+// should avoid relying on this as this exposes unstable library implementation
+// details.
 OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get0_chain(X509_STORE_CTX *ctx);
+
+// X509_STORE_CTX_get1_chain behaves like |X509_STORE_CTX_get0_chain| but
+// returns a newly-allocated |STACK_OF(X509)| containing the completed chain,
+// with each certificate's reference count incremented. Callers must free the
+// result with |sk_X509_pop_free| and |X509_free| when done.
 OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get1_chain(X509_STORE_CTX *ctx);
+
 OPENSSL_EXPORT void X509_STORE_CTX_set_cert(X509_STORE_CTX *c, X509 *x);
 OPENSSL_EXPORT void X509_STORE_CTX_set_chain(X509_STORE_CTX *c,
                                              STACK_OF(X509) *sk);
@@ -3907,7 +3951,8 @@
 // preserves the default behavior. Returning one when |ok| is zero will proceed
 // past some error. The callback may inspect |ctx| and the error queue to
 // attempt to determine the current stage of certificate verification, but this
-// is often unreliable.
+// is often unreliable. When synthesizing an error, callbacks should use
+// |X509_STORE_CTX_set_error| to set a corresponding error.
 //
 // WARNING: Do not use this function. It is extremely fragile and unpredictable.
 // This callback exposes implementation details of certificate verification,