Сертификат, сгенерированный путем подписания CSR с BouncyCastle, считается недоверенным
Я борюсь со следующей проблемой:
У меня есть 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 отсутствует в списке доверенных авторизаций Windows, но на самом деле он также есть.
Даже странно: если я подпишу CSR с использованием OpenSSL, цепочка сертификатов выглядит хорошо. У меня также была идея, что процесс импорта пары ключей CA из OpenSSL в хранилище ключей Java через PKCS12 в качестве промежуточного формата не удался, но на самом деле, если я экспортирую сертификат CA из хранилища ключей Java и открою его с сертификатом Windows зритель, это показано как доверенный...
ОБНОВЛЕНИЕ: Для тех, кто знаком с ASN.1, вот два закодированных сертификата. Один сделан с помощью BouncyCastle и НЕ является доверенным, другой подписан тем же ключом CA с OpenSSL и является доверенным. Их можно декодировать с помощью такого инструмента: декодер ASN.1. Я был бы очень признателен, если бы кто-нибудь мог просмотреть эти декодированные данные и сказать мне, что может вызвать разницу между ними.
Этому НЕ доверяют:
-----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:
Благодаря ответу Бруно цепочка сертификатов теперь выглядит нормально и генерируется следующий сертификат:
-----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?
Ps сертификат 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-----
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
В результате он не сможет сопоставить неправильного эмитента с DN субъекта CA.
К несчастью, 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());