【问题标题】:Certificate generated through CSR signing with BouncyCastle considered untrusted通过 CSR 与 BouncyCastle 签名生成的证书被认为是不受信任的
【发布时间】:2014-05-19 11:14:45
【问题描述】:

我正在努力解决以下问题:

我有一个 CSR,我正在使用此代码签名:

@Override
public X509Certificate signCSR( Reader pemcsr, int validityDays ) throws APIException
{
try ( PEMParser reader = new PEMParser( pemcsr ) )
{

  KeyStore keystore = getKeyStore();

  Properties cryptoProps = getCryptoProperties();

  String caKeyAlias = cryptoProps.getProperty( PROPERTY_KEYSTORE_CA_CERT_ALIAS );
  String caKeyPassword = cryptoProps.getProperty( PROPERTY_KEYSTORE_CA_CERT_PASSWORD );

  PrivateKey cakey = (PrivateKey) keystore.getKey( caKeyAlias, caKeyPassword.toCharArray() );
  X509Certificate cacert = (X509Certificate) keystore.getCertificate( caKeyAlias );

  PKCS10CertificationRequest csr = (PKCS10CertificationRequest) reader.readObject();

  AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA1withRSA" );
  AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find( sigAlgId );
  X500Name issuer = new X500Name( cacert.getSubjectX500Principal().getName() );
  BigInteger serial = new BigInteger( 32, new SecureRandom() );
  Date from = new Date();
  Date to = new Date( System.currentTimeMillis() + ( validityDays * 86400000L ) );

  DigestCalculator digCalc = new BcDigestCalculatorProvider().get( new AlgorithmIdentifier( OIWObjectIdentifiers.idSHA1 ) );
  X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils( digCalc );

  X509v3CertificateBuilder certgen = new X509v3CertificateBuilder( issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo() );

  // Basic Constraints
  // certgen.addExtension( Extension.basicConstraints, true, new
  // BasicConstraints( 0 ) );

  // Subject Key Identifier
  // certgen.addExtension( Extension.subjectKeyIdentifier, false,
  // x509ExtensionUtils.createSubjectKeyIdentifier(
  // csr.getSubjectPublicKeyInfo() ) );

  // Authority Key Identifier
  // byte[] caKeyEncoded = cacert.getPublicKey().getEncoded();
  // SubjectPublicKeyInfo caSubjectPublicKeyInfo =
  // SubjectPublicKeyInfo.getInstance( caKeyEncoded );
  // certgen.addExtension( Extension.authorityKeyIdentifier, false,
  // x509ExtensionUtils.createAuthorityKeyIdentifier( caSubjectPublicKeyInfo
  // ) );

  // Key Usage
  // certgen.addExtension( Extension.keyUsage, false, new KeyUsage(
  // KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign )
  // );

  ContentSigner signer = new BcRSAContentSignerBuilder( sigAlgId, digAlgId ).build( PrivateKeyFactory.createKey( cakey.getEncoded() ) );

  // ContentSigner signer = new JcaContentSignerBuilder(
  // "SHA1WithRSAEncryption" ).setProvider( "BC" ).build( cakey );

  X509CertificateHolder holder = certgen.build( signer );

  return new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( holder );
}
catch ( NoSuchAlgorithmException | KeyStoreException | CertificateException | OperatorCreationException | UnrecoverableKeyException | CertIOException e )
{
  throw new APIException( API_ERROR_CODE.CRYPTOGRAPHY_ERROR, e );
}
catch ( IOException e )
{
  throw new APIException( API_ERROR_CODE.IO_ERROR, e );
}

}

这样运行成功。但是,当我尝试使用以下方法检查密钥时:

KeyStore ks = getKeyStore();

  TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
  trustManagerFactory.init( ks );

  for ( TrustManager trustManager : trustManagerFactory.getTrustManagers() )
  {
    if ( trustManager instanceof X509TrustManager )
    {
      X509TrustManager x509TrustManager = (X509TrustManager) trustManager;
      x509TrustManager.checkClientTrusted( new X509Certificate[] { certificate }, "RSA" );
    }
  }

...它失败并出现 CertificateException。请注意,我在这里使用的是非常相同的密钥库,这意味着我正在使用的 CA 密钥包含在其中。为什么会这样?

顺便说一句,奇怪的是,当我使用 Windows 的证书查看器打开生成的签名证书时,它确实显示了颁发 CA 的名称,但它的条目没有出现在证书链中。似乎 CA 根证书不存在于 Windows 受信任的权限列表中,但实际上它也存在。

更奇怪的是:如果我使用 OpenSSL 签署 CSR,证书链看起来没问题。我也有这样的想法,即通过 PKCS12 将 CA 密钥对从 OpenSSL 导入 Java 密钥库作为中间格式的过程并不成功,但实际上如果我从 Java 密钥库导出 CA 证书并使用 Windows 证书打开它查看器,它显示为受信任...

更新:对于熟悉 ASN.1 的人,这里有两个编码证书。一个是用 BouncyCastle 制作的,不受信任,另一个是由与 OpenSSL 相同的 CA 密钥签名的,并且是受信任的。可以使用这样的工具对它们进行解码:ASN.1 decoder 如果有人可以查看这些解码数据并告诉我可能导致它们之间的差异的原因,我将非常感激。

这个不可信:

-----BEGIN CERTIFICATE-----
MIIC6TCCAlKgAwIBAgIESdsI/TANBgkqhkiG9w0BAQUFADCBgzEgMB4GCSqGSIb3
DQEJARYRdGVzdGNhQHRlc3RjYS5jb20xEDAOBgNVBAMMB1Rlc3QgQ0ExEDAOBgNV
BAsMB1Rlc3QgQ0ExEDAOBgNVBAoMB1Rlc3QgQ0ExDTALBgNVBAcMBFdpZW4xDTAL
BgNVBAgMBFdpZW4xCzAJBgNVBAYTAkFUMB4XDTE0MDUxOTExNTYwM1oXDTE1MDUx
OTExNTYwM1owajELMAkGA1UEBhMCVUsxCzAJBgNVBAgTAlBiMQswCQYDVQQHEwJC
cDETMBEGA1UEChMKZmdmZ2ZnZGZnZDEPMA0GA1UECxMGYWJjZGVmMRswGQYDVQQD
DBJwZXRlcnZlbG9zeV90aWdyaXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCdL7taENsONBazc2iMDV5nw9ACP5mevmnzPwOJRUcd5GlGgry/iSa3tTwL
l6Um3zNc4X0m5nVVskKeJE4dTvYFV3+vJlEKCra86yQfa6XkGllU4EG6SdG8lRhE
Btk1QbOQZKrUz77IdOWWOUvIsNxtDDQcUhnrSjSxHohdoe/yoCl+60RBdjrgUrRo
uctSHFPvVt2uZaVM2rAVovx56vvJHOag2++rcvXaOh9WHvdwRAIZt/4aOv2O4jdI
jKdRrmF8dOudjR89wIeVjX9fvyvx+hw+ZolUio9GOVKLlBcYno6lEupHLUDK9ECs
W8F6y65nYGlm9/0G0+gB7K1yy1dBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAKJpM
7AbkWBH3ho1YV0d1glJvefQ1xaXGpDfd+Tzf3+cR1o3+YxxEyuYvBbiQ/MBxKD9/
hsFCqEWzOfu2lAZ+/6uHvt7BCEGhaLdWKXehoaIw/kEMeISIUDFbKORCsKJNbYRB
xgqBXGglTQ4gVXMDRBxzOmButN31j1VDt55gvn4=
-----END CERTIFICATE-----

这是受信任的,它是使用理论上相同的 CA 证书但通过 OpenSSL 生成的:

-----BEGIN CERTIFICATE-----
MIIC+TCCAmICAhI4MA0GCSqGSIb3DQEBBQUAMIGDMQswCQYDVQQGEwJBVDENMAsG
A1UECAwEV2llbjENMAsGA1UEBwwEV2llbjEQMA4GA1UECgwHVGVzdCBDQTEQMA4G
A1UECwwHVGVzdCBDQTEQMA4GA1UEAwwHVGVzdCBDQTEgMB4GCSqGSIb3DQEJARYR
dGVzdGNhQHRlc3RjYS5jb20wHhcNMTQwNTE0MTkzMTAzWhcNMTUwNTA5MTkzMTAz
WjCBgDELMAkGA1UEBhMCSFUxETAPBgNVBAgTCEJ1ZGFwZXN0MREwDwYDVQQHEwhC
dWRhcGVzdDEWMBQGA1UEChMNTWVyY2hhbnQgVGVzdDEWMBQGA1UECxMNTWVyY2hh
bnQgVGVzdDEbMBkGA1UEAwwScGV0ZXJ2ZWxvc3lfdGlncmlzMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1vuY4MQ5b9Jb0MiyEuCrR4E+7VgmrvEwlswO
aMIF4H6i538PwPml5dbqx/3whxR/BcQJuJYWI/Hh7xxGS7FvSQ+DNhzxv9TpECKS
/5OZNm+JikPZwTiwrS/Cf4NP+ZcXOjtVZp6ngVtTarn3NC/J7gJVYaHVVO4NbUkt
kCYhdfCXg71QiJ42RWMjMC9tJFrrlfem+SVzh8yMtUCBKm7nbMjQ6LngawjTzDK8
2Zcdqwdzvt2pcYcsYSViO5j5t/r7rIDGjRkjJqRSEiJMOvn0W+sdTdmFoZbyj7Qe
pgyCyf28uFyCO9QZro337D8klPLXaWJOwPDXXiuYOTDYAjBVbwIDAQABMA0GCSqG
SIb3DQEBBQUAA4GBAGU60GVjR+2oEiJMSe1CKU7gf+bGuxaCxXQTzVQLU652i1sp
Fv56o6jnLtw46/rQydNKX4GBH022B/BDEPAQQiQv31YKQAoWtBZod0SRonogcx7p
AULacoma9QEgHSX0l+2yEn42/qo7o0pAmmewJlsCnHVIqI0eU8x1XbCEAf53
-----END CERTIFICATE-----

更新 2:

感谢 Bruno 的回答,证书链现在看起来没问题,并且生成了以下证书:

-----BEGIN CERTIFICATE-----
MIIC6TCCAlKgAwIBAgIEI2vbpTANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC
QVQxDTALBgNVBAgMBFdpZW4xDTALBgNVBAcMBFdpZW4xEDAOBgNVBAoMB1Rlc3Qg
Q0ExEDAOBgNVBAsMB1Rlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0ExIDAeBgkqhkiG
9w0BCQEWEXRlc3RjYUB0ZXN0Y2EuY29tMB4XDTE0MDUyMDA3MzkyMFoXDTE1MDUy
MDA3MzkyMFowajELMAkGA1UEBhMCVUsxCzAJBgNVBAgTAlBiMQswCQYDVQQHEwJC
cDETMBEGA1UEChMKZmdmZ2ZnZGZnZDEPMA0GA1UECxMGYWJjZGVmMRswGQYDVQQD
DBJwZXRlcnZlbG9zeV90aWdyaXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCdL7taENsONBazc2iMDV5nw9ACP5mevmnzPwOJRUcd5GlGgry/iSa3tTwL
l6Um3zNc4X0m5nVVskKeJE4dTvYFV3+vJlEKCra86yQfa6XkGllU4EG6SdG8lRhE
Btk1QbOQZKrUz77IdOWWOUvIsNxtDDQcUhnrSjSxHohdoe/yoCl+60RBdjrgUrRo
uctSHFPvVt2uZaVM2rAVovx56vvJHOag2++rcvXaOh9WHvdwRAIZt/4aOv2O4jdI
jKdRrmF8dOudjR89wIeVjX9fvyvx+hw+ZolUio9GOVKLlBcYno6lEupHLUDK9ECs
W8F6y65nYGlm9/0G0+gB7K1yy1dBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAIdFF
h6uLY7ioKQ3O0c4cZHHjRA0HTlWjih8P2xvXY/V9jF914BT7OW52UJ16tQaJlOf+
mAeeBDq9srKnkmOQp3mCejVnkyVZF8pOOzNbqSVzylt0Csg2twnxZ0NcM63Oda5b
YSQI8+arryxykLWkHWH8i/6rPCDCtbAHBo7fSeQ=
-----END CERTIFICATE-----

但是,上面的 TrustManager 代码拒绝了它。如果我绕过 TrustManager 并执行以下操作:

 KeyStore ks = getKeyStore();

  Enumeration<String> aliases = ks.aliases();

  while ( aliases.hasMoreElements() )
  {
    String alias = aliases.nextElement();
    Certificate currentCert = ks.getCertificate( alias );
    try
    {
      certificate.verify( currentCert.getPublicKey() );
      return true;
    }
    catch ( Exception e )
    {
      // the certificate cannot be verified with this key.
    }
  }

  return false;

...它通过了。有人知道为什么 TrustManager 检查失败了吗?

附: CA 证书如下所示:

-----BEGIN CERTIFICATE-----
MIICfzCCAegCCQCU+Ah6M5qQGTANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC
QVQxDTALBgNVBAgMBFdpZW4xDTALBgNVBAcMBFdpZW4xEDAOBgNVBAoMB1Rlc3Qg
Q0ExEDAOBgNVBAsMB1Rlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0ExIDAeBgkqhkiG
9w0BCQEWEXRlc3RjYUB0ZXN0Y2EuY29tMB4XDTE0MDQyMzA3MjYzNFoXDTI0MDQy
MDA3MjYzNFowgYMxCzAJBgNVBAYTAkFUMQ0wCwYDVQQIDARXaWVuMQ0wCwYDVQQH
DARXaWVuMRAwDgYDVQQKDAdUZXN0IENBMRAwDgYDVQQLDAdUZXN0IENBMRAwDgYD
VQQDDAdUZXN0IENBMSAwHgYJKoZIhvcNAQkBFhF0ZXN0Y2FAdGVzdGNhLmNvbTCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAldKTo8iqF52dsOwln0Oppu+ODiaG
R4T7Znrca4Cs5FBQOmuMwqUP6ilW115p/WvkBHhm8dZyVACPKdshEfhh4VFAW5r2
mJnosYgjafQpTEv83sc938DwtK6iikZ0uvdBJKG/IuYblNq9TPMLFeTYjD8mgf9j
m6JOvA/Q9J4nRW0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQB8ACYeC+zjV/KqxPr1
cyzfJP9xfUnxDTEKUJS2YVuxJqpfbHeUtvKoN89BfY07XWdnj8cgMDfJp10Kdc2A
clwP2lVDtOgHZS07UUW98q9FKQ33mLHIn0nDKNwTo5VH8t/NJVeMFuZPAbFiI2gj
KH2sTU2GNNvKC4jHh0PS+OZFtg==
-----END CERTIFICATE-----

【问题讨论】:

  • CertEx 是否包含一些解释? Java 的 TrustManager 可能很挑剔,例如它可能希望工作的 OCSP 响应者检查吊销状态。尝试使用-Dcom.sun.net.ssl.checkRevocation=false 选项运行它。
  • 嗨!异常包含“PKIX 路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到到请求目标的有效证书路径”。它必须与Windows证书查看器也找不到证书路径的事实有关。似乎签名证书与原始证书有所不同......有没有办法可以确认新证书的颁发者和我的 CA 根证书实际上是相同的?
  • 附注checkRevocation=false 没有帮助。
  • 你现在遇到什么异常?
  • 同理:PKIX 路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到请求目标的有效认证路径

标签: java ssl bouncycastle x509


【解决方案1】:

如果您查看两个证书中的颁发者 DN,它们不匹配(openssl x509 -text 的输出):

Issuer: C=AT, ST=Wien, L=Wien, O=Test CA, OU=Test CA, CN=Test CA/emailAddress=testca@testca.com

Issuer: emailAddress=testca@testca.com, CN=Test CA, OU=Test CA, O=Test CA, L=Wien, ST=Wien, C=AT

因此,它无法将错误的颁发者与 CA 的主题 DN 匹配。

不幸的是,X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName()) 没有达到您的预期。 RDN 的顺序是相反的。通常,从字符串表示重新构建 DN 可能会失败,因为有不同的方法将 ASN.1 表示序列化为字符串。 Java 的X500Principal 有多种可用于getName(...) 的格式,它甚至提供了一种将您自己的OID 提供给字符串映射的方法(对于更模糊的OID)。 emailAddress 的分隔方式也会导致问题(注意它是用逗号或斜线分隔的)。

相反,从编码形式构建 X500Name,这应该始终有效:

X500Name x500Name = X500Name.getInstance(cert
                        .getSubjectX500Principal().getEncoded());

【讨论】:

  • 这听起来很合理,感谢您指出这一点。我明天一上班就试试,如果成功了,请标记你的答案。
  • 谢谢,我将此标记为已接受的答案,现在证书链看起来没问题。不过,Tru​​stManager 拒绝了它,我已经用一些细节更新了这个问题。
  • +1 - 花了 3 天时间弄清楚为什么我的 CA 在迁移到 BC15 后不信任新生成的客户端证书。你的回答完全搞定了。谢谢
  • 对我来说反之亦然: - 给定一个带有主题的 OpenSSL CaCert (CN=..., O=..., OU=..., DC=..., DC=.. ., DC=...)。 (1) cacert.getSubjectX500Principal().getEncoded();颠倒顺序和 (2) cacert.getSubjectX500Principal().getName();反映了正确的顺序。但是(2)两者都有问题,即键前的空格被删除,因此某些验证器中的字符串比较对于 issuer-dn 和 cacert subject-dn 再次失败
  • @jforge,从技术上讲,getEncoded() 是“正确”的顺序。 getSubjectX500Principal().getName() 是 RFC 2253 文本序列化,它可以为您提供一种使用字符串进行比较的方法,前提是您使用的其他工具也遵循该格式。
猜你喜欢
  • 2012-04-23
  • 2017-04-21
  • 2015-11-28
  • 2015-10-28
  • 2012-09-23
  • 2013-02-08
  • 2020-09-21
  • 2018-04-16
  • 1970-01-01
相关资源
最近更新 更多