private SignatureSecurityInformation VerifyInMem(Stream verifiedContent, Stream signed, SignatureSecurityInformation outer) { trace.TraceEvent(TraceEventType.Information, 0, "Verifying the {0} signature in memory", outer == null ? "inner" : "outer"); try { CmsSignedData signedData; try { signedData = new CmsSignedData(signed); trace.TraceEvent(TraceEventType.Verbose, 0, "Read the cms header"); } catch (Exception e) { trace.TraceEvent(TraceEventType.Error, 0, "The message isn't a CMS Signed Data message: {0}", e.Message); throw new InvalidMessageException("The message isn't a triple wrapped message", e); } if (verifiedContent != null) { signedData.SignedContent.Write(verifiedContent); trace.TraceEvent(TraceEventType.Verbose, 0, "Copied the signed data"); } IX509Store certs = signedData.GetCertificates("COLLECTION"); SignerInformationStore signerInfos = signedData.GetSignerInfos(); return Verify(signerInfos, certs, outer); } catch(CmsException cmse) { throw new InvalidMessageException("The message isn't a triple wrapped message", cmse); } }
private SignatureSecurityInformation VerifyStreaming(Stream verifiedContent, Stream signed, SignatureSecurityInformation outer) { trace.TraceEvent(TraceEventType.Information, 0, "Verifying the {0} signature streamed", outer == null ? "inner" : "outer"); try { CmsSignedDataParser signedData; try { signedData = new CmsSignedDataParser(signed); trace.TraceEvent(TraceEventType.Verbose, 0, "Read the cms header"); } catch (Exception e) { trace.TraceEvent(TraceEventType.Error, 0, "The message isn't a CMS Signed Data message: {0}", e.Message); throw new InvalidMessageException("The message isn't a triple wrapped message", e); } signedData.GetSignedContent().ContentStream.CopyTo(verifiedContent); trace.TraceEvent(TraceEventType.Verbose, 0, "Copied the signed data & calculated the message digest"); IX509Store certs = signedData.GetCertificates("COLLECTION"); SignerInformationStore signerInfos = signedData.GetSignerInfos(); return Verify(signerInfos, certs, outer); } catch (CmsException cmse) { if (cmse.Message.Contains("RSAandMGF1 not supported")) { throw new NotSupportedException("RSA-PSS not supported with streaming in case of raw signatures"); } throw new InvalidMessageException("The message isn't a triple wrapped message", cmse); } }
private SignatureSecurityInformation Verify(SignerInformationStore signerInfos, IX509Store certs, SignatureSecurityInformation outer) { trace.TraceEvent(TraceEventType.Information, 0, "Verifying the {0} signature information", outer != null ? "outer" : "inner"); SignatureSecurityInformation result = new SignatureSecurityInformation(); //Check if signed (only allow single signatures) SignerInformation signerInfo = null; IEnumerator iterator = signerInfos.GetSigners().GetEnumerator(); if (!iterator.MoveNext()) { result.securityViolations.Add(SecurityViolation.NotSigned); trace.TraceEvent(TraceEventType.Warning, 0, "Although it is a correct CMS file it isn't signed"); return result; } signerInfo = (SignerInformation)iterator.Current; trace.TraceEvent(TraceEventType.Verbose, 0, "Found signature, with signer ID = issuer {0} and serial number {1}", signerInfo.SignerID.Issuer, signerInfo.SignerID.SerialNumber); if (iterator.MoveNext()) { trace.TraceEvent(TraceEventType.Error, 0, "Found more then one signature, this isn't supported (yet)"); throw new InvalidMessageException("An eHealth compliant message can have only one signer"); } //check if signer used correct digest algorithm int i = 0; bool found = false; StringBuilder algos = new StringBuilder(); while (!found && i < EteeActiveConfig.Unseal.SignatureAlgorithms.Count) { Oid algoDigest = EteeActiveConfig.Unseal.SignatureAlgorithms[i].DigestAlgorithm; Oid algoEnc = EteeActiveConfig.Unseal.SignatureAlgorithms[i++].EncryptionAlgorithm; algos.Append(algoDigest.Value + " (" + algoDigest.FriendlyName + ") + " + algoEnc.Value + " (" + algoEnc.FriendlyName + "), "); found = (algoDigest.Value == signerInfo.DigestAlgOid) && (algoEnc.Value == signerInfo.EncryptionAlgOid); } if (!found) { result.securityViolations.Add(SecurityViolation.NotAllowedSignatureDigestAlgorithm); trace.TraceEvent(TraceEventType.Warning, 0, "The signature digest + encryption algorithm {0} + {1} isn't allowed, only {2} are", signerInfo.DigestAlgOid, signerInfo.EncryptionAlgOid, algos); } trace.TraceEvent(TraceEventType.Verbose, 0, "Verified the signature digest and encryption algorithm"); //Find the singing certificate and relevant info Org.BouncyCastle.X509.X509Certificate signerCert = null; if (certs.GetMatches(null).Count > 0) { //We got certificates, so lets find the signer IEnumerator signerCerts = certs.GetMatches(signerInfo.SignerID).GetEnumerator(); if (!signerCerts.MoveNext()) { //found no certificate result.securityViolations.Add(SecurityViolation.NotFoundSigner); trace.TraceEvent(TraceEventType.Warning, 0, "Could not find the signer certificate"); return result; } //Getting the first certificate signerCert = (Org.BouncyCastle.X509.X509Certificate)signerCerts.Current; trace.TraceEvent(TraceEventType.Verbose, 0, "Found the signer certificate: {0}", signerCert.SubjectDN.ToString()); //Check if the outer certificate matches the inner certificate if (outer != null) { Org.BouncyCastle.X509.X509Certificate authCert = DotNetUtilities.FromX509Certificate(outer.Subject.Certificate); trace.TraceEvent(TraceEventType.Verbose, 0, "Comparing The signer certificate {0} ({1}) with the authentication certificate {2} ({3})", signerCert.SubjectDN, signerCert.IssuerDN, authCert.SubjectDN, authCert.IssuerDN); //_safe_ check if the serial numbers of the subject name are equal and they have the same issuer if (!authCert.SubjectDN.GetOidList().Contains(X509Name.SerialNumber) || !signerCert.SubjectDN.GetOidList().Contains(X509Name.SerialNumber) || authCert.SubjectDN.GetValueList(X509Name.SerialNumber).Count != 1 || signerCert.SubjectDN.GetValueList(X509Name.SerialNumber).Count != 1 || !authCert.SubjectDN.GetValueList(X509Name.SerialNumber)[0].Equals(signerCert.SubjectDN.GetValueList(X509Name.SerialNumber)[0]) || !authCert.IssuerDN.Equals(signerCert.IssuerDN)) { result.securityViolations.Add(SecurityViolation.SubjectDoesNotMachEnvelopingSubject); trace.TraceEvent(TraceEventType.Warning, 0, "The signer certificate {0} ({1}) does not match the authentication certificate {2} ({3})", signerCert.SubjectDN, signerCert.IssuerDN, authCert.SubjectDN, authCert.IssuerDN); } } if (signerCerts.MoveNext()) { //found several certificates... trace.TraceEvent(TraceEventType.Error, 0, "Several certificates correspond to the signer"); throw new NotSupportedException("More then one certificate found that corresponds to the sender information in the message, this isn't supported by the library"); } } else { if (outer == null) { trace.TraceEvent(TraceEventType.Error, 0, "The outer signature does not contain any certificates"); throw new InvalidMessageException("The outer signature is missing certifcates"); } //The subject is the same as the outer result.Subject = outer.Subject; signerCert = DotNetUtilities.FromX509Certificate(result.Subject.Certificate); trace.TraceEvent(TraceEventType.Verbose, 0, "An already validated certificates was provided: {0}", signerCert.SubjectDN.ToString()); //Additional check, is the authentication certificate also valid for signatures? if (!DotNetUtilities.FromX509Certificate(result.Subject.Certificate).GetKeyUsage()[1]) { result.Subject.securityViolations.Add(CertSecurityViolation.NotValidForUsage); trace.TraceEvent(TraceEventType.Warning, 0, "The key usage did not have the correct usage flag set"); } } //verify the signature itself result.SignatureValue = signerInfo.GetSignature(); if (!signerInfo.Verify(signerCert.GetPublicKey())) { result.securityViolations.Add(SecurityViolation.NotSignatureValid); trace.TraceEvent(TraceEventType.Warning, 0, "The signature value was invalid"); } trace.TraceEvent(TraceEventType.Verbose, 0, "Signature value verification finished"); //Get the signing time bool hasSigningTime = false; DateTime signingTime = DateTime.UtcNow; if (signerInfo != null && signerInfo.SignedAttributes != null) { Org.BouncyCastle.Asn1.Cms.Attribute time = signerInfo.SignedAttributes[CmsAttributes.SigningTime]; if (time != null && time.AttrValues.Count > 0) { hasSigningTime = true; result.SigningTime = Org.BouncyCastle.Asn1.Cms.Time.GetInstance(time.AttrValues[0]).Date; signingTime = result.SigningTime.Value; trace.TraceEvent(TraceEventType.Verbose, 0, "The CMS message contains a signing time: {0}", result.SigningTime); } } //Validating signature info if (this.level == null && result.Subject == null) { if (outer == null) result.Subject = CertVerifier.VerifyAuth(signerCert, signingTime, certs, null, null, false, false); else result.Subject = CertVerifier.VerifySign(signerCert, signingTime, certs, null, null, false, false); return result; } //Get the embedded CRLs and OCSPs IList<CertificateList> crls = new List<CertificateList>(); IList<BasicOcspResponse> ocsps = new List<BasicOcspResponse>(); if (signerInfo != null && signerInfo.UnsignedAttributes != null) { trace.TraceEvent(TraceEventType.Verbose, 0, "The CMS message contains unsigned attributes"); Org.BouncyCastle.Asn1.Cms.Attribute revocationValuesList = signerInfo.UnsignedAttributes[PkcsObjectIdentifiers.IdAAEtsRevocationValues]; if (revocationValuesList != null && revocationValuesList.AttrValues.Count > 0) { trace.TraceEvent(TraceEventType.Verbose, 0, "The CMS message contains Revocation Values"); RevocationValues revocationValues = RevocationValues.GetInstance(revocationValuesList.AttrValues[0]); if (revocationValues.GetCrlVals() != null) { crls = new List<CertificateList>(revocationValues.GetCrlVals()); trace.TraceEvent(TraceEventType.Verbose, 0, "Found {0} CRL's in the message", crls.Count); } if (revocationValues.GetOcspVals() != null) { ocsps = new List<BasicOcspResponse>(revocationValues.GetOcspVals()); trace.TraceEvent(TraceEventType.Verbose, 0, "Found {0} OCSP's in the message", ocsps.Count); } } } //check for a time-stamp, even if not required DateTime validationTime; TimeStampToken tst = null; bool isSigingTimeValidated; if (signerInfo != null && signerInfo.UnsignedAttributes != null) { trace.TraceEvent(TraceEventType.Verbose, 0, "The CMS message contains unsigned attributes"); Org.BouncyCastle.Asn1.Cms.Attribute tstList = signerInfo.UnsignedAttributes[PkcsObjectIdentifiers.IdAASignatureTimeStampToken]; if (tstList != null && tstList.AttrValues.Count > 0) { trace.TraceEvent(TraceEventType.Verbose, 0, "The CMS message contains the Signature Time Stamp Token: {0}", Convert.ToBase64String(tstList.AttrValues[0].GetEncoded())); tst = tstList.AttrValues[0].GetEncoded().ToTimeStampToken(); } } if (tst == null) { //Retrieve the time-mark if needed by the level only if ((this.level & Level.T_Level) == Level.T_Level) { if (outer == null) { //we are in the outer signature, so we need a time-mark (or time-stamp, but we checked that already) if (timemarkauthority == null) { trace.TraceEvent(TraceEventType.Error, 0, "Not time-mark authority is provided while there is not embedded time-stamp, the level includes T-Level and it isn't an inner signature"); throw new InvalidMessageException("The message does not contain a time-stamp and there is not time-mark authority provided while T-Level is required"); } trace.TraceEvent(TraceEventType.Verbose, 0, "Requesting time-mark for message signed by {0}, signed on {1} and with signature value {2}", signerCert.SubjectDN, signingTime, signerInfo.GetSignature()); validationTime = timemarkauthority.GetTimemark(new X509Certificate2(signerCert.GetEncoded()), signingTime, signerInfo.GetSignature()).ToUniversalTime(); trace.TraceEvent(TraceEventType.Verbose, 0, "The validated time is the return time-mark which is: {0}", validationTime); } else { //we are in the inner signature, we check the signing time against the outer signatures signing time validationTime = outer.SigningTime.Value; trace.TraceEvent(TraceEventType.Verbose, 0, "The validated time is the outer signature singing time which is: {0}", validationTime); } } else { isSigingTimeValidated = false; validationTime = signingTime; trace.TraceEvent(TraceEventType.Verbose, 0, "There is not validated provided, nor is it retrieved because of the level"); } } else { //Check the time-stamp if (!tst.IsMatch(new MemoryStream(signerInfo.GetSignature()))) { trace.TraceEvent(TraceEventType.Warning, 0, "The time-stamp does not match the message"); result.securityViolations.Add(SecurityViolation.InvalidTimestamp); } Timestamp stamp; if ((this.level & Level.A_level) == Level.A_level) { //TODO::follow the chain of A-timestamps until the root (now we assume the signature time-stamp is the root) trace.TraceEvent(TraceEventType.Verbose, 0, "Validating the time-stamp against the current time for arbitration reasons"); stamp = tst.Validate(ref crls, ref ocsps, null); } else { trace.TraceEvent(TraceEventType.Verbose, 0, "Validating the time-stamp against the time-stamp time since no arbitration is needed"); stamp = tst.Validate(ref crls, ref ocsps); } result.TimestampRenewalTime = stamp.RenewalTime; trace.TraceEvent(TraceEventType.Verbose, 0, "The time-stamp must be renewed on {0}", result.TimestampRenewalTime); //we get the time from the time-stamp validationTime = stamp.Time; trace.TraceEvent(TraceEventType.Verbose, 0, "The validated time is the time-stamp time which is on {0}", validationTime); if (!hasSigningTime) { trace.TraceEvent(TraceEventType.Information, 0, "Implicit signing time {0} is replaced with time-stamp time {1}", signingTime, tst.TimeStampInfo.GenTime); signingTime = stamp.Time; } if (stamp.TimestampStatus.Count(x => x.Status != X509ChainStatusFlags.NoError) > 0) { trace.TraceEvent(TraceEventType.Warning, 0, "The time-stamp is invalid with {0} errors, including {1}: {2}", stamp.TimestampStatus.Count, stamp.TimestampStatus[0].Status, stamp.TimestampStatus[0].StatusInformation); isSigingTimeValidated = false; result.securityViolations.Add(SecurityViolation.InvalidTimestamp); } } //lest check if the signing time is in line with the validation time (obtained from time-mark, outer signature or time-stamp) if (validationTime > (signingTime + EteeActiveConfig.ClockSkewness + Settings.Default.TimestampGracePeriod) || validationTime < (signingTime - EteeActiveConfig.ClockSkewness)) { trace.TraceEvent(TraceEventType.Warning, 0, "The validated time {0} is not in line with the signing time {1} with a grace period of {2}", validationTime, signingTime, Settings.Default.TimestampGracePeriod); isSigingTimeValidated = false; result.securityViolations.Add(SecurityViolation.SealingTimeInvalid); } else { trace.TraceEvent(TraceEventType.Verbose, 0, "The validated time {0} is in line with the signing time {1}", validationTime, signingTime); isSigingTimeValidated = true; } //If the subject is already provided, so we don't have to do the next check if (result.Subject != null) return result; //check the status on the available info, for unseal it does not matter if it is B-Level or LT-Level. if (outer == null) result.Subject = CertVerifier.VerifyAuth(signerCert, signingTime, certs, crls, ocsps, true, isSigingTimeValidated); else result.Subject = CertVerifier.VerifySign(signerCert, signingTime, certs, crls, ocsps, true, isSigingTimeValidated); return result; }