|
1 |
| -/* $OpenBSD: cert.c,v 1.162 2025/06/19 06:47:57 tb Exp $ */ |
| 1 | +/* $OpenBSD: cert.c,v 1.163 2025/06/19 10:26:34 tb Exp $ */ |
2 | 2 | /*
|
3 | 3 | * Copyright (c) 2022 Theo Buehler <[email protected]>
|
4 | 4 | * Copyright (c) 2021 Job Snijders <[email protected]>
|
|
30 | 30 |
|
31 | 31 | #include "extern.h"
|
32 | 32 |
|
| 33 | +extern ASN1_OBJECT *bgpsec_oid; /* id-kp-bgpsec-router Key Purpose */ |
33 | 34 | extern ASN1_OBJECT *certpol_oid; /* id-cp-ipAddr-asNumber cert policy */
|
34 | 35 | extern ASN1_OBJECT *carepo_oid; /* 1.3.6.1.5.5.7.48.5 (caRepository) */
|
35 | 36 | extern ASN1_OBJECT *manifest_oid; /* 1.3.6.1.5.5.7.48.10 (rpkiManifest) */
|
@@ -736,6 +737,151 @@ cert_check_subject_and_issuer(const char *fn, const X509 *x)
|
736 | 737 | return 1;
|
737 | 738 | }
|
738 | 739 |
|
| 740 | +/* |
| 741 | + * Check the cert's purpose: the cA bit in basic constraints distinguishes |
| 742 | + * between TA/CA and EE/BGPsec router and the key usage bits must match. |
| 743 | + * TAs are self-signed, CAs not self-issued, EEs have no extended key usage, |
| 744 | + * BGPsec router have id-kp-bgpsec-router OID. |
| 745 | + */ |
| 746 | +static enum cert_purpose |
| 747 | +cert_check_purpose(const char *fn, X509 *x) |
| 748 | +{ |
| 749 | + BASIC_CONSTRAINTS *bc = NULL; |
| 750 | + EXTENDED_KEY_USAGE *eku = NULL; |
| 751 | + const X509_EXTENSION *ku; |
| 752 | + int crit, ext_flags, i, is_ca, ku_idx; |
| 753 | + enum cert_purpose purpose = CERT_PURPOSE_INVALID; |
| 754 | + |
| 755 | + if (!x509_cache_extensions(x, fn)) |
| 756 | + goto out; |
| 757 | + |
| 758 | + ext_flags = X509_get_extension_flags(x); |
| 759 | + |
| 760 | + /* Key usage must be present and critical. KU bits are checked below. */ |
| 761 | + if ((ku_idx = X509_get_ext_by_NID(x, NID_key_usage, -1)) < 0) { |
| 762 | + warnx("%s: RFC 6487, section 4.8.4: missing KeyUsage", fn); |
| 763 | + goto out; |
| 764 | + } |
| 765 | + if ((ku = X509_get_ext(x, ku_idx)) == NULL) { |
| 766 | + warnx("%s: RFC 6487, section 4.8.4: missing KeyUsage", fn); |
| 767 | + goto out; |
| 768 | + } |
| 769 | + if (!X509_EXTENSION_get_critical(ku)) { |
| 770 | + warnx("%s: RFC 6487, section 4.8.4: KeyUsage not critical", fn); |
| 771 | + goto out; |
| 772 | + } |
| 773 | + |
| 774 | + /* This weird API can return 0, 1, 2, 4, 5 but can't error... */ |
| 775 | + if ((is_ca = X509_check_ca(x)) > 1) { |
| 776 | + if (is_ca == 4) |
| 777 | + warnx("%s: RFC 6487: sections 4.8.1 and 4.8.4: " |
| 778 | + "no basic constraints, but keyCertSign set", fn); |
| 779 | + else |
| 780 | + warnx("%s: unexpected legacy certificate", fn); |
| 781 | + goto out; |
| 782 | + } |
| 783 | + |
| 784 | + if (is_ca) { |
| 785 | + bc = X509_get_ext_d2i(x, NID_basic_constraints, &crit, NULL); |
| 786 | + if (bc == NULL) { |
| 787 | + if (crit != -1) |
| 788 | + warnx("%s: RFC 6487 section 4.8.1: " |
| 789 | + "error parsing basic constraints", fn); |
| 790 | + else |
| 791 | + warnx("%s: RFC 6487 section 4.8.1: " |
| 792 | + "missing basic constraints", fn); |
| 793 | + goto out; |
| 794 | + } |
| 795 | + if (crit != 1) { |
| 796 | + warnx("%s: RFC 6487 section 4.8.1: Basic Constraints " |
| 797 | + "must be marked critical", fn); |
| 798 | + goto out; |
| 799 | + } |
| 800 | + if (bc->pathlen != NULL) { |
| 801 | + warnx("%s: RFC 6487 section 4.8.1: Path Length " |
| 802 | + "Constraint must be absent", fn); |
| 803 | + goto out; |
| 804 | + } |
| 805 | + |
| 806 | + if (X509_get_key_usage(x) != (KU_KEY_CERT_SIGN | KU_CRL_SIGN)) { |
| 807 | + warnx("%s: RFC 6487 section 4.8.4: key usage violation", |
| 808 | + fn); |
| 809 | + goto out; |
| 810 | + } |
| 811 | + |
| 812 | + if (X509_get_extended_key_usage(x) != UINT32_MAX) { |
| 813 | + warnx("%s: RFC 6487 section 4.8.5: EKU not allowed", |
| 814 | + fn); |
| 815 | + goto out; |
| 816 | + } |
| 817 | + |
| 818 | + /* |
| 819 | + * EXFLAG_SI means that issuer and subject are identical. |
| 820 | + * EXFLAG_SS is SI plus the AKI is absent or matches the SKI. |
| 821 | + * Thus, exactly the trust anchors should have EXFLAG_SS set |
| 822 | + * and we should never see EXFLAG_SI without EXFLAG_SS. |
| 823 | + */ |
| 824 | + if ((ext_flags & EXFLAG_SS) != 0) |
| 825 | + purpose = CERT_PURPOSE_TA; |
| 826 | + else if ((ext_flags & EXFLAG_SI) == 0) |
| 827 | + purpose = CERT_PURPOSE_CA; |
| 828 | + else |
| 829 | + warnx("%s: RFC 6487, section 4.8.3: " |
| 830 | + "self-issued cert with AKI-SKI mismatch", fn); |
| 831 | + goto out; |
| 832 | + } |
| 833 | + |
| 834 | + if ((ext_flags & EXFLAG_BCONS) != 0) { |
| 835 | + warnx("%s: Basic Constraints ext in non-CA cert", fn); |
| 836 | + goto out; |
| 837 | + } |
| 838 | + |
| 839 | + if ((ext_flags & (EXFLAG_SI | EXFLAG_SS)) != 0) { |
| 840 | + warnx("%s: EE cert must not be self-issued or self-signed", fn); |
| 841 | + goto out; |
| 842 | + } |
| 843 | + |
| 844 | + if (X509_get_key_usage(x) != KU_DIGITAL_SIGNATURE) { |
| 845 | + warnx("%s: RFC 6487 section 4.8.4: KU must be digitalSignature", |
| 846 | + fn); |
| 847 | + goto out; |
| 848 | + } |
| 849 | + |
| 850 | + /* |
| 851 | + * EKU is only defined for BGPsec Router certs and must be absent from |
| 852 | + * EE certs. |
| 853 | + */ |
| 854 | + eku = X509_get_ext_d2i(x, NID_ext_key_usage, &crit, NULL); |
| 855 | + if (eku == NULL) { |
| 856 | + if (crit != -1) |
| 857 | + warnx("%s: error parsing EKU", fn); |
| 858 | + else |
| 859 | + purpose = CERT_PURPOSE_EE; /* EKU absent */ |
| 860 | + goto out; |
| 861 | + } |
| 862 | + if (crit != 0) { |
| 863 | + warnx("%s: EKU: extension must not be marked critical", fn); |
| 864 | + goto out; |
| 865 | + } |
| 866 | + |
| 867 | + /* |
| 868 | + * Per RFC 8209, section 3.1.3.2 the id-kp-bgpsec-router OID must be |
| 869 | + * present and others are allowed, which we don't need to recognize. |
| 870 | + * This matches RFC 5280, section 4.2.1.12. |
| 871 | + */ |
| 872 | + for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { |
| 873 | + if (OBJ_cmp(bgpsec_oid, sk_ASN1_OBJECT_value(eku, i)) == 0) { |
| 874 | + purpose = CERT_PURPOSE_BGPSEC_ROUTER; |
| 875 | + break; |
| 876 | + } |
| 877 | + } |
| 878 | + |
| 879 | + out: |
| 880 | + BASIC_CONSTRAINTS_free(bc); |
| 881 | + EXTENDED_KEY_USAGE_free(eku); |
| 882 | + return purpose; |
| 883 | +} |
| 884 | + |
739 | 885 | /*
|
740 | 886 | * Lightweight version of cert_parse_pre() for EE certs.
|
741 | 887 | * Parses the two RFC 3779 extensions, and performs some sanity checks.
|
@@ -766,7 +912,7 @@ cert_parse_ee_cert(const char *fn, int talid, X509 *x)
|
766 | 912 | * Check issuance, basic constraints and (extended) key usage bits are
|
767 | 913 | * appropriate for an EE cert. Covers RFC 6487, 4.8.1, 4.8.4, 4.8.5.
|
768 | 914 | */
|
769 |
| - if ((cert->purpose = x509_get_purpose(x, fn)) != CERT_PURPOSE_EE) { |
| 915 | + if ((cert->purpose = cert_check_purpose(fn, x)) != CERT_PURPOSE_EE) { |
770 | 916 | warnx("%s: expected EE cert, got %s", fn,
|
771 | 917 | purpose2str(cert->purpose));
|
772 | 918 | goto out;
|
@@ -968,7 +1114,7 @@ cert_parse_pre(const char *fn, const unsigned char *der, size_t len)
|
968 | 1114 | goto out;
|
969 | 1115 |
|
970 | 1116 | /* Validation on required fields. */
|
971 |
| - cert->purpose = x509_get_purpose(x, fn); |
| 1117 | + cert->purpose = cert_check_purpose(fn, x); |
972 | 1118 | switch (cert->purpose) {
|
973 | 1119 | case CERT_PURPOSE_TA:
|
974 | 1120 | /* XXX - caller should indicate if it expects TA or CA cert */
|
|
0 commit comments