diff --git a/plugins/crypto/openssl/ua_pki_openssl.c b/plugins/crypto/openssl/ua_pki_openssl.c index c741efcf820..dbfc94e4bad 100644 --- a/plugins/crypto/openssl/ua_pki_openssl.c +++ b/plugins/crypto/openssl/ua_pki_openssl.c @@ -390,196 +390,217 @@ UA_ReloadCertFromFolder (CertContext * ctx) { #endif /* end of __linux__ */ -static UA_StatusCode -UA_X509_Store_CTX_Error_To_UAError (int opensslErr) { - UA_StatusCode ret; - - switch (opensslErr) { - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_CRL_NOT_YET_VALID: - case X509_V_ERR_CRL_HAS_EXPIRED: - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: - case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: - ret = UA_STATUSCODE_BADCERTIFICATETIMEINVALID; - break; - case X509_V_ERR_CERT_REVOKED: - ret = UA_STATUSCODE_BADCERTIFICATEREVOKED; - break; - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; - break; - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - ret = UA_STATUSCODE_BADSECURITYCHECKSFAILED; - break; - case X509_V_ERR_UNABLE_TO_GET_CRL: - ret = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN; - break; - default: - ret = UA_STATUSCODE_BADCERTIFICATEINVALID; - break; - } - return ret; - } - -static UA_StatusCode -UA_CertificateVerification_Verify (void * verificationContext, - const UA_ByteString * certificate) { - X509_STORE_CTX* storeCtx; - X509_STORE* store; - CertContext * ctx; - UA_StatusCode ret; - int opensslRet; - X509 * certificateX509 = NULL; +static const unsigned char openssl_PEM_PRE[28] = "-----BEGIN CERTIFICATE-----"; + +/* Extract the leaf certificate from a bytestring that may contain an entire chain */ +static X509 * +openSSLLoadLeafCertificate(UA_ByteString cert, size_t *offset) { + if(cert.length <= *offset) + return NULL; + cert.length -= *offset; + cert.data += *offset; + + /* Detect DER encoding. Extract the encoding length and cut. */ + if(cert.length >= 4 && cert.data[0] == 0x30 && cert.data[1] == 0x82) { + /* The certificate length is encoded after the magic bytes */ + size_t certLen = 4; /* Magic numbers + length bytes */ + certLen += (size_t)(((uint16_t)cert.data[2]) << 8); + certLen += cert.data[3]; + if(certLen > cert.length) + return NULL; + cert.length = certLen; + *offset += certLen; + const UA_Byte *dataPtr = cert.data; + return d2i_X509(NULL, &dataPtr, (long)cert.length); + } + + /* Assume PEM encoding. Detect multiple certificates and cut. */ + if(cert.length > 27 * 4) { + const unsigned char *match = + UA_Bstrstr(openssl_PEM_PRE, 27, &cert.data[27*2], cert.length - (27*2)); + if(match) + cert.length = (uintptr_t)(match - cert.data); + } + *offset += cert.length; + + BIO *bio = BIO_new_mem_buf((void *) cert.data, (int)cert.length); + X509 *result = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free(bio); + return result; +} - if (verificationContext == NULL) { - return UA_STATUSCODE_BADINTERNALERROR; - } - ctx = (CertContext *) verificationContext; - - store = X509_STORE_new(); - storeCtx = X509_STORE_CTX_new(); - - if (store == NULL || storeCtx == NULL) { - ret = UA_STATUSCODE_BADOUTOFMEMORY; - goto cleanup; - } -#ifdef __linux__ - ret = UA_ReloadCertFromFolder (ctx); - if (ret != UA_STATUSCODE_GOOD) { - goto cleanup; - } -#endif +/* The bytestring might contain an entire certificate chain. The first + * stack-element is the leaf certificate itself. The remaining ones are + * potential issuer certificates. */ +static STACK_OF(X509) * +openSSLLoadCertificateStack(const UA_ByteString cert) { + size_t offset = 0; + X509 *x509 = NULL; + STACK_OF(X509) *result = sk_X509_new_null(); + if(!result) + return NULL; + while((x509 = openSSLLoadLeafCertificate(cert, &offset))) { + sk_X509_push(result, x509); + } + return result; +} - certificateX509 = UA_OpenSSL_LoadCertificate(certificate); - if (certificateX509 == NULL) { - ret = UA_STATUSCODE_BADCERTIFICATEINVALID; - goto cleanup; - } - - X509_STORE_set_flags(store, 0); - opensslRet = X509_STORE_CTX_init (storeCtx, store, certificateX509, - ctx->skIssue); - if (opensslRet != 1) { - ret = UA_STATUSCODE_BADINTERNALERROR; - goto cleanup; - } -#if defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT < 0x10100000L - (void) X509_STORE_CTX_trusted_stack (storeCtx, ctx->skTrusted); -#else - (void) X509_STORE_CTX_set0_trusted_stack (storeCtx, ctx->skTrusted); -#endif +/* Return the first matching issuer candidate AFTER prev */ +static X509 * +openSSLFindNextIssuer(CertContext *ctx, STACK_OF(X509) *stack, X509 *x509, X509 *prev) { + /* First check issuers from the stack - provided in the same bytestring as + * the certificate. This can also return x509 itself. */ + do { + int size = sk_X509_num(stack); + for(int i = 0; i < size; i++) { + X509 *candidate = sk_X509_value(stack, i); + if(X509_check_issued(candidate, x509) != 0) + continue; + if(prev == NULL) + return candidate; + prev = NULL; + } + /* Switch to search in the ctx->skIssue list */ + stack = (stack != ctx->skIssue) ? ctx->skIssue : NULL; + } while(stack); + return NULL; +} - /* Set crls to ctx */ - if (sk_X509_CRL_num (ctx->skCrls) > 0) { - X509_STORE_CTX_set0_crls (storeCtx, ctx->skCrls); +static UA_Boolean +openSSLCheckRevoked(CertContext *ctx, X509 *cert) { + const ASN1_INTEGER *sn = X509_get0_serialNumber(cert); + const X509_NAME *in = X509_get_issuer_name(cert); + int size = sk_X509_CRL_num(ctx->skCrls); + for(int i = 0; i < size; i++) { + /* The crl contains a list of serial numbers from the same issuer */ + X509_CRL *crl = sk_X509_CRL_value(ctx->skCrls, i); + if(X509_NAME_cmp(in, X509_CRL_get_issuer(crl)) != 0) + continue; + STACK_OF(X509_REVOKED) *rs = X509_CRL_get_REVOKED(crl); + int rsize = sk_X509_REVOKED_num(rs); + for(int j = 0; j < rsize; j++) { + X509_REVOKED *r = sk_X509_REVOKED_value(rs, j); + if(ASN1_INTEGER_cmp(sn, X509_REVOKED_get0_serialNumber(r)) == 0) + return true; + } } + return false; +} - /* Set flag to check if the certificate has an invalid signature */ - X509_STORE_CTX_set_flags (storeCtx, X509_V_FLAG_CHECK_SS_SIGNATURE); - - if (X509_check_issued(certificateX509,certificateX509) != X509_V_OK) { - X509_STORE_CTX_set_flags (storeCtx, X509_V_FLAG_CRL_CHECK); - } +#define UA_OPENSSL_MAX_CHAIN_LENGTH 10 - /* This condition will check whether the certificate is a User certificate or a CA certificate. - * If the KU_KEY_CERT_SIGN and KU_CRL_SIGN of key_usage are set, then the certificate shall be - * condidered as CA Certificate and cannot be used to establish a connection. Refer the test case - * CTT/Security/Security Certificate Validation/029.js for more details */ - /** \todo Can the ca-parameter of X509_check_purpose can be used? */ - if(X509_check_purpose(certificateX509, X509_PURPOSE_CRL_SIGN, 0) && X509_check_ca(certificateX509)) { - return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; - } +static UA_StatusCode +openSSL_verifyChain(CertContext *ctx, STACK_OF(X509) *stack, X509 **old_issuers, + X509 *x509, int depth) { + /* Maxiumum chain length */ + if(depth == UA_OPENSSL_MAX_CHAIN_LENGTH) + return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; + + /* Verification Step: Validity Period */ + ASN1_TIME *notBefore = X509_get_notBefore(x509); + ASN1_TIME *notAfter = X509_get_notAfter(x509); + if(X509_cmp_current_time(notBefore) != -1 || X509_cmp_current_time(notAfter) != 1) + return UA_STATUSCODE_BADCERTIFICATETIMEINVALID; + + /* Verification Step: Revocation Check */ + if(openSSLCheckRevoked(ctx, x509)) + return UA_STATUSCODE_BADCERTIFICATEREVOKED; + + /* Is the certificate in the trust list? If yes, then we are done. */ + for(int i = 0; i < sk_X509_num(ctx->skTrusted); i++) { + if(X509_cmp(x509, sk_X509_value(ctx->skTrusted, i)) == 0) + return UA_STATUSCODE_GOOD; + } + + /* Return the most specific error code. BADCERTIFICATECHAININCOMPLETE is + * returned only if all possible chains are incomplete. */ + X509 *issuer = NULL; + UA_StatusCode ret = UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; + while(ret != UA_STATUSCODE_GOOD) { + /* Find the issuer. We jump back here to find a different path if a + * subsequent check fails. */ + issuer = openSSLFindNextIssuer(ctx, stack, x509, issuer); + if(!issuer) + break; - opensslRet = X509_verify_cert (storeCtx); - if (opensslRet == 1) { - ret = UA_STATUSCODE_GOOD; + /* Detect (endless) loops of issuers */ + for(int i = 0; i < depth; i++) { + if(old_issuers[i] == issuer) + return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; + } + old_issuers[depth] = issuer; + + /* Verification Step: Signature */ + int opensslRet = X509_verify(x509, X509_get0_pubkey(issuer)); + if(opensslRet == -1) { + return UA_STATUSCODE_BADCERTIFICATEINVALID; /* Ill-formed signature */ + } else if(opensslRet == 0) { + ret = UA_STATUSCODE_BADCERTIFICATEINVALID; /* Wrong issuer, try again */ + continue; + } - /* Check if the not trusted certificate has a CRL file. If there is no CRL file available for the corresponding - * parent certificate then return status code UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN. Refer the test - * case CTT/Security/Security Certificate Validation/002.js */ - if (X509_check_issued(certificateX509,certificateX509) != X509_V_OK) { - /* Free X509_STORE_CTX and reuse it for certification verification */ - if (storeCtx != NULL) { - X509_STORE_CTX_free(storeCtx); - } + /* We have found the issuer certificate used for the signature. Recurse + * to the next certificate in the chain (verify the current issuer). */ + ret = openSSL_verifyChain(ctx, stack, old_issuers, issuer, depth + 1); - /* Initialised X509_STORE_CTX sructure*/ - storeCtx = X509_STORE_CTX_new(); + /* Problems where x509 != leaf are reported as "untrusted" without the + * detailed reason */ + if(ret != UA_STATUSCODE_GOOD && + ret != UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE) + ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; + } - /* Sets up X509_STORE_CTX structure for a subsequent verification operation */ - X509_STORE_set_flags(store, 0); - X509_STORE_CTX_init (storeCtx, store, certificateX509,ctx->skIssue); + return ret; +} - /* Set trust list to ctx */ - (void) X509_STORE_CTX_trusted_stack (storeCtx, ctx->skTrusted); +/* This follows Part 6, 6.1.3 Determining if a Certificate is trusted. + * It defines a sequence of steps for certificate verification. */ +static UA_StatusCode +UA_CertificateVerification_Verify(void *verificationContext, + const UA_ByteString *certificate) { + if(!verificationContext || !certificate) + return UA_STATUSCODE_BADINTERNALERROR; - /* Set crls to ctx */ - X509_STORE_CTX_set0_crls (storeCtx, ctx->skCrls); + UA_StatusCode ret = UA_STATUSCODE_GOOD; + CertContext *ctx = (CertContext *)verificationContext; - /* Set flags for CRL check */ - X509_STORE_CTX_set_flags (storeCtx, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); +#ifdef __linux__ + ret = UA_ReloadCertFromFolder(ctx); + if(ret != UA_STATUSCODE_GOOD) + return ret; +#endif - opensslRet = X509_verify_cert (storeCtx); - if (opensslRet != 1) { - opensslRet = X509_STORE_CTX_get_error (storeCtx); - if (opensslRet == X509_V_ERR_UNABLE_TO_GET_CRL) { - ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN; - } else { - ret = UA_X509_Store_CTX_Error_To_UAError (opensslRet); - } - } - } + /* Verification Step: Certificate Structure */ + STACK_OF(X509) *stack = openSSLLoadCertificateStack(*certificate); + if(!stack || sk_X509_num(stack) < 1) { + if(stack) + sk_X509_pop_free(stack, X509_free); + return UA_STATUSCODE_BADCERTIFICATEINVALID; + } + + /* Verification Step: Certificate Usage + * Check whether the certificate is a User certificate or a CA certificate. + * If the KU_KEY_CERT_SIGN and KU_CRL_SIGN of key_usage are set, then the + * certificate shall be condidered as CA Certificate and cannot be used to + * establish a connection. Refer the test case CTT/Security/Security + * Certificate Validation/029.js for more details */ + X509 *leaf = sk_X509_value(stack, 0); + if(X509_check_purpose(leaf, X509_PURPOSE_CRL_SIGN, 0) && X509_check_ca(leaf)) { + sk_X509_pop_free(stack, X509_free); + return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; } - else { - opensslRet = X509_STORE_CTX_get_error (storeCtx); - - /* Check the issued certificate of a CA that is not trusted but available */ - if(opensslRet == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN){ - int trusted_cert_len = sk_X509_num(ctx->skTrusted); - int cmpVal; - X509 *trusted_cert; - const ASN1_OCTET_STRING *trusted_cert_keyid; - const ASN1_OCTET_STRING *remote_cert_keyid; - - for (int i = 0; i < trusted_cert_len; i++) { - trusted_cert = sk_X509_value(ctx->skTrusted, i); - - /* Fetch the Subject key identifier of the certificate in trust list */ - trusted_cert_keyid = X509_get0_subject_key_id(trusted_cert); - - /* Fetch the Subject key identifier of the remote certificate */ - remote_cert_keyid = X509_get0_subject_key_id(certificateX509); - - if(trusted_cert_keyid && remote_cert_keyid) { - /* Check remote certificate is present in the trust list */ - cmpVal = ASN1_OCTET_STRING_cmp(trusted_cert_keyid, remote_cert_keyid); - if(cmpVal == 0) { - ret = UA_STATUSCODE_GOOD; - goto cleanup; - } - } - } - } - /* Return expected OPCUA error code */ - ret = UA_X509_Store_CTX_Error_To_UAError (opensslRet); - } -cleanup: - if (store != NULL) { - X509_STORE_free (store); - } - if (storeCtx != NULL) { - X509_STORE_CTX_free (storeCtx); - } - if (certificateX509 != NULL) { - X509_free (certificateX509); - } + /* These steps are performed outside of this method. + * Because we need the server or client context. + * - Security Policy + * - Host Name + * - URI */ + + /* Verification Step: Build Certificate Chain + * We perform the checks for each certificate inside. */ + X509 *old_issuers[UA_OPENSSL_MAX_CHAIN_LENGTH]; + ret = openSSL_verifyChain(ctx, stack, old_issuers, leaf, 0); + sk_X509_pop_free(stack, X509_free); return ret; }