private static bool CertMatchesIds(X509Certificate2 signerCert, EssCertId certId, EssCertIdV2 certId2) { Debug.Assert(signerCert != null); Debug.Assert(certId != null || certId2 != null); byte[] serialNumber = null; if (certId != null) { Span <byte> thumbprint = stackalloc byte[20]; if (!signerCert.TryGetCertHash(HashAlgorithmName.SHA1, thumbprint, out int written) || written != thumbprint.Length || !thumbprint.SequenceEqual(certId.Hash.Span)) { return(false); } if (certId.IssuerSerial != null) { serialNumber = signerCert.GetSerialNumber(); Array.Reverse(serialNumber); if (!IssuerAndSerialMatch( certId.IssuerSerial.Value, signerCert.IssuerName.RawData, serialNumber)) { return(false); } } } if (certId2 != null) { HashAlgorithmName alg; // SHA-2-512 is the biggest we know about. Span <byte> thumbprint = stackalloc byte[512 / 8]; try { alg = Helpers.GetDigestAlgorithm(certId2.HashAlgorithm.Algorithm); if (signerCert.TryGetCertHash(alg, thumbprint, out int written)) { thumbprint = thumbprint.Slice(0, written); } else { Debug.Fail( $"TryGetCertHash did not fit in {thumbprint.Length} for hash {certId2.HashAlgorithm.Algorithm.Value}"); thumbprint = signerCert.GetCertHash(alg); } } catch (CryptographicException) { return(false); } if (!thumbprint.SequenceEqual(certId2.Hash.Span)) { return(false); } if (certId2.IssuerSerial != null) { if (serialNumber == null) { serialNumber = signerCert.GetSerialNumber(); Array.Reverse(serialNumber); } if (!IssuerAndSerialMatch( certId2.IssuerSerial.Value, signerCert.IssuerName.RawData, serialNumber)) { return(false); } } } return(true); }
private static bool TryGetCertIds(SignerInfo signer, out EssCertId certId, out EssCertIdV2 certId2) { // RFC 5035 says that SigningCertificateV2 (contains ESSCertIDv2) is a signed // attribute, with OID 1.2.840.113549.1.9.16.2.47, and that it must not be multiply defined. // RFC 2634 says that SigningCertificate (contains ESSCertID) is a signed attribute, // with OID 1.2.840.113549.1.9.16.2.12, and that it must not be multiply defined. certId = null; certId2 = null; foreach (CryptographicAttributeObject attrSet in signer.SignedAttributes) { string setOid = attrSet.Oid?.Value; if (setOid != null && setOid != Oids.SigningCertificate && setOid != Oids.SigningCertificateV2) { continue; } foreach (AsnEncodedData attr in attrSet.Values) { string attrOid = attr.Oid?.Value; if (attrOid == Oids.SigningCertificate) { if (certId != null) { return(false); } try { SigningCertificateAsn signingCert = AsnSerializer.Deserialize <SigningCertificateAsn>(attr.RawData, AsnEncodingRules.BER); if (signingCert.Certs.Length < 1) { return(false); } // The first one is the signing cert, the rest constrain the chain. certId = signingCert.Certs[0]; } catch (CryptographicException) { return(false); } } if (attrOid == Oids.SigningCertificateV2) { if (certId2 != null) { return(false); } try { SigningCertificateV2Asn signingCert = AsnSerializer.Deserialize <SigningCertificateV2Asn>(attr.RawData, AsnEncodingRules.BER); if (signingCert.Certs.Length < 1) { return(false); } // The first one is the signing cert, the rest constrain the chain. certId2 = signingCert.Certs[0]; } catch (CryptographicException) { return(false); } } } } return(certId2 != null || certId != null); }
private static bool CheckCertificate( X509Certificate2 tsaCertificate, SignerInfo signer, EssCertId certId, EssCertIdV2 certId2, Rfc3161TimestampTokenInfo tokenInfo) { Debug.Assert(tsaCertificate != null); Debug.Assert(signer != null); Debug.Assert(tokenInfo != null); // certId and certId2 are allowed to be null, they get checked in CertMatchesIds. if (!CertMatchesIds(tsaCertificate, certId, certId2)) { return(false); } // Nothing in RFC3161 actually mentions checking the certificate's validity // against the TSTInfo timestamp value, but it seems sensible. // // Accuracy is ignored here, for better replicability in user code. if (tsaCertificate.NotAfter < tokenInfo.Timestamp || tsaCertificate.NotBefore > tokenInfo.Timestamp) { return(false); } // https://tools.ietf.org/html/rfc3161#section-2.3 // // The TSA MUST sign each time-stamp message with a key reserved // specifically for that purpose. A TSA MAY have distinct private keys, // e.g., to accommodate different policies, different algorithms, // different private key sizes or to increase the performance. The // corresponding certificate MUST contain only one instance of the // extended key usage field extension as defined in [RFC2459] Section // 4.2.1.13 with KeyPurposeID having value: // // id-kp-timeStamping. This extension MUST be critical. using (var ekuExts = tsaCertificate.Extensions.OfType <X509EnhancedKeyUsageExtension>().GetEnumerator()) { if (!ekuExts.MoveNext()) { return(false); } X509EnhancedKeyUsageExtension ekuExt = ekuExts.Current; if (!ekuExt.Critical) { return(false); } bool hasPurpose = false; foreach (Oid oid in ekuExt.EnhancedKeyUsages) { if (oid.Value == Oids.TimeStampingPurpose) { hasPurpose = true; break; } } if (!hasPurpose) { return(false); } if (ekuExts.MoveNext()) { return(false); } } try { signer.CheckSignature(new X509Certificate2Collection(tsaCertificate), true); return(true); } catch (CryptographicException) { return(false); } }