|
39 | 39 | import org.bouncycastle.asn1.ASN1EncodableVector; |
40 | 40 | import org.bouncycastle.asn1.ASN1Encoding; |
41 | 41 | import org.bouncycastle.asn1.DERSet; |
| 42 | +import org.bouncycastle.asn1.DERUTF8String; |
42 | 43 | import org.bouncycastle.asn1.cms.Attribute; |
43 | 44 | import org.bouncycastle.asn1.cms.AttributeTable; |
44 | 45 | import org.bouncycastle.asn1.cms.CMSAttributes; |
45 | 46 | import org.bouncycastle.asn1.cms.Time; |
46 | 47 | import org.bouncycastle.asn1.x500.X500Name; |
| 48 | +import org.bouncycastle.asn1.x500.X500NameBuilder; |
| 49 | +import org.bouncycastle.asn1.x500.style.BCStyle; |
47 | 50 | import org.bouncycastle.asn1.x509.BasicConstraints; |
48 | 51 | import org.bouncycastle.asn1.x509.Extension; |
49 | 52 | import org.bouncycastle.asn1.x509.ExtendedKeyUsage; |
@@ -164,6 +167,101 @@ public void testMultiEmail() throws Exception |
164 | 167 | assertTrue(addresses.contains("domain-confidentiality-authority@bekb.ch ")); |
165 | 168 | } |
166 | 169 |
|
| 170 | + public void testEmailAddressFoldingIsLocaleIndependent() throws Exception |
| 171 | + { |
| 172 | + // Turkish rules fold 'I' to the dotless U+0131; the class used to capture the default locale at load time. |
| 173 | + Locale defaultLocale = Locale.getDefault(); |
| 174 | + |
| 175 | + try |
| 176 | + { |
| 177 | + Locale.setDefault(new Locale("tr", "TR")); |
| 178 | + |
| 179 | + X500Name signDN = new X500Name("CN=Ian Echidna, E=IAN@EXAMPLE.ORG, O=Bouncy Castle, C=AU"); |
| 180 | + |
| 181 | + assertTrue("From address must match the certificate address whatever the default locale", |
| 182 | + validateEmailBinding(signDN, "ian@example.org").isValidSignature()); |
| 183 | + } |
| 184 | + finally |
| 185 | + { |
| 186 | + Locale.setDefault(defaultLocale); |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + public void testEmailAddressFoldingIsAsciiOnly() throws Exception |
| 191 | + { |
| 192 | + // U+212A KELVIN SIGN lower cases to 'k', U+0130 to 'i' (Turkish) or "i̇", so neither may match an ASCII mailbox. |
| 193 | + checkNonAsciiEmailRejected("Kate@example.org", "kate@example.org"); |
| 194 | + checkNonAsciiEmailRejected("İan@example.org", "ian@example.org"); |
| 195 | + } |
| 196 | + |
| 197 | + private void checkNonAsciiEmailRejected(String certEmail, String fromEmail) throws Exception |
| 198 | + { |
| 199 | + X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); |
| 200 | + nameBuilder.addRDN(BCStyle.CN, "Test Signer"); |
| 201 | + nameBuilder.addRDN(BCStyle.EmailAddress, new DERUTF8String(certEmail)); |
| 202 | + nameBuilder.addRDN(BCStyle.O, "Bouncy Castle"); |
| 203 | + nameBuilder.addRDN(BCStyle.C, "AU"); |
| 204 | + |
| 205 | + SignedMailValidator.ValidationResult result = validateEmailBinding(nameBuilder.build(), fromEmail); |
| 206 | + |
| 207 | + assertTrue(result.isVerifiedSignature()); |
| 208 | + assertFalse(result.isValidSignature()); |
| 209 | + |
| 210 | + Iterator it = result.getErrors().iterator(); |
| 211 | + boolean found = false; |
| 212 | + while (it.hasNext()) |
| 213 | + { |
| 214 | + if (((ErrorBundle)it.next()).getId().equals("SignedMailValidator.emailFromCertMismatch")) |
| 215 | + { |
| 216 | + found = true; |
| 217 | + } |
| 218 | + } |
| 219 | + assertTrue("expected emailFromCertMismatch for " + fromEmail, found); |
| 220 | + } |
| 221 | + |
| 222 | + private static SignedMailValidator.ValidationResult validateEmailBinding(X500Name signDN, String from) |
| 223 | + throws Exception |
| 224 | + { |
| 225 | + long now = System.currentTimeMillis(); |
| 226 | + long day = 1000L * 60 * 60 * 24; |
| 227 | + Date notBefore = new Date(now - day); |
| 228 | + Date notAfter = new Date(now + 100 * day); |
| 229 | + |
| 230 | + X500Name caDN = new X500Name("CN=Test CA, O=Bouncy Castle, C=AU"); |
| 231 | + |
| 232 | + KeyPair caKP = CMSTestUtil.makeKeyPair(); |
| 233 | + KeyPair signKP = CMSTestUtil.makeKeyPair(); |
| 234 | + |
| 235 | + X509Certificate caCert = buildCert(caDN, caKP.getPublic(), caDN, caKP, notBefore, notAfter, true); |
| 236 | + X509Certificate signCert = buildCert(signDN, signKP.getPublic(), caDN, caKP, notBefore, notAfter, false); |
| 237 | + |
| 238 | + List certList = new ArrayList(); |
| 239 | + certList.add(signCert); |
| 240 | + certList.add(caCert); |
| 241 | + |
| 242 | + SMIMESignedGenerator gen = new SMIMESignedGenerator(); |
| 243 | + gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC") |
| 244 | + .build("SHA256withRSA", signKP.getPrivate(), signCert)); |
| 245 | + gen.addCertificates(new JcaCertStore(certList)); |
| 246 | + |
| 247 | + MimeMultipart signedMsg = gen.generate(SMIMETestUtil.makeMimeBodyPart("Hello world!\n")); |
| 248 | + |
| 249 | + Session session = Session.getDefaultInstance(System.getProperties(), null); |
| 250 | + MimeMessage msg = new MimeMessage(session); |
| 251 | + msg.setFrom(new InternetAddress(from)); |
| 252 | + msg.setRecipient(Message.RecipientType.TO, new InternetAddress("example@bouncycastle.org")); |
| 253 | + msg.setContent(signedMsg, signedMsg.getContentType()); |
| 254 | + msg.saveChanges(); |
| 255 | + |
| 256 | + Set trust = new HashSet(); |
| 257 | + trust.add(new TrustAnchor(caCert, null)); |
| 258 | + |
| 259 | + PKIXParameters params = new PKIXParameters(trust); |
| 260 | + params.setRevocationEnabled(false); |
| 261 | + |
| 262 | + return firstResult(new SignedMailValidator(msg, params)); |
| 263 | + } |
| 264 | + |
167 | 265 | public void testExtKeyUsage() throws Exception |
168 | 266 | { |
169 | 267 | String message = "validator.extKeyUsage.eml"; |
@@ -343,8 +441,15 @@ private static X509Certificate buildCert(String subDN, java.security.PublicKey s |
343 | 441 | KeyPair issKP, Date notBefore, Date notAfter, boolean ca) |
344 | 442 | throws Exception |
345 | 443 | { |
346 | | - JcaX509v3CertificateBuilder b = new JcaX509v3CertificateBuilder(new X500Name(issDN), |
347 | | - BigInteger.valueOf(System.nanoTime()), notBefore, notAfter, new X500Name(subDN), subPub); |
| 444 | + return buildCert(new X500Name(subDN), subPub, new X500Name(issDN), issKP, notBefore, notAfter, ca); |
| 445 | + } |
| 446 | + |
| 447 | + private static X509Certificate buildCert(X500Name subDN, java.security.PublicKey subPub, X500Name issDN, |
| 448 | + KeyPair issKP, Date notBefore, Date notAfter, boolean ca) |
| 449 | + throws Exception |
| 450 | + { |
| 451 | + JcaX509v3CertificateBuilder b = new JcaX509v3CertificateBuilder(issDN, |
| 452 | + BigInteger.valueOf(System.nanoTime()), notBefore, notAfter, subDN, subPub); |
348 | 453 | b.addExtension(Extension.basicConstraints, false, new BasicConstraints(ca)); |
349 | 454 | if (ca) |
350 | 455 | { |
|
0 commit comments