iOS MDM SCEP PKIOperation: сервер SCEP возвратил неверный ответ
Предисловие
Я работаю над реализацией сервера iOS MDM в Node.js и использую node-forge для PKI. Часть регистрации устройства требует использования SCEP.
вопрос
Устройства в настоящее время выходят из строя на начальном operation=PKIOperation
CSR запрос на мой сервер. Сообщение об ошибке на устройствах выглядит довольно расплывчато:
May 18 14:39:46 iPad-2 Preferences[27999] <Notice>: (Error) MC: Install profile data, interactive error. Error: NSError:
Desc : Profile Installation Failed
Sugg : The SCEP server returned an invalid response.
US Desc: Profile Installation Failed
US Sugg: The SCEP server returned an invalid response.
Domain : MCInstallationErrorDomain
Code : 4001
Type : MCFatalError
...Underlying error:
NSError:
Desc : The SCEP server returned an invalid response.
US Desc: The SCEP server returned an invalid response.
Domain : MCSCEPErrorDomain
Code : 22013
Type : MCFatalError
Extra info:
{
isPrimary = 1;
}
Я попытался смоделировать свою обработку CSR на основе обзора протокола регистрации простых сертификатов и следующего примера кода Ruby ( найденного здесь и в других местах):
def sign_PKI(data)
p7sign = OpenSSL::PKCS7.new(data)
store = OpenSSL::X509::Store.new
p7sign.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY)
signers = p7sign.signers
p7enc = OpenSSL::PKCS7.new(p7sign.data)
# Certificate Signing Request
csr = p7enc.decrypt(SSL.key, SSL.certificate)
# Signed Certificate
cert = self.sign_certificate(csr)
degenerate_pkcs7 = OpenSSL::PKCS7.new()
degenerate_pkcs7.type="signed"
degenerate_pkcs7.certificates=[cert]
enc_cert = OpenSSL::PKCS7.encrypt(p7sign.certificates, degenerate_pkcs7.to_der,
OpenSSL::Cipher::Cipher::new("des-ede3-cbc"), OpenSSL::PKCS7::BINARY)
reply = OpenSSL::PKCS7.sign(SSL.certificate, SSL.key, enc_cert.to_der, [], OpenSSL::PKCS7::BINARY)
return Certificate.new(reply.to_der, "application/x-pki-message")
end
Наконец, вот моя реализация с использованием Node.js и node-forge:
function pkiOperationScepOperationHandler(req, reply) {
//
// |req.query.message| should contain a Base64 encoded PKCS#7 package.
// The SignedData portion is PKCS#7 EnvelopedData encrypted with the CA
// public key we gave the client in GetCACert. Once decrypted, we have
// ourselves the client's CSR.
//
if(!req.query.message) {
return reply('The CA could not validate the request').code(403);
}
const msgBuffer = new Buffer(req.query.message, 'base64');
let p7Message;
try {
p7Message = forge.pkcs7.messageFromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(msgBuffer, 'binary')
)
);
const p7EnvelopedData = forge.pkcs7.messageFromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(new Buffer(p7Message.rawCapture.content.value[0].value[0].value, 'binary'), 'binary')
)
);
p7EnvelopedData.decrypt(p7EnvelopedData.recipients[0], conf.serverConfig.caPrivateKey);
// p7EnvelopedData should contain a PKCS#10 CSR
const csrDataBuffer = new Buffer(p7EnvelopedData.content.getBytes(), 'binary');
const csr = forge.pki.certificationRequestFromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(csrDataBuffer, 'binary')
),
true // computeHash
);
//
// Create a new cert based on the CSR and sign it
//
// See https://github.com/digitalbazaar/forge/issues/154
//
const signedCert = forge.pki.createCertificate();
signedCert.serialNumber = Date.now().toString();
signedCert.validity.notBefore = new Date();
signedCert.validity.notAfter = new Date();
signedCert.validity.notAfter.setFullYear(signedCert.validity.notBefore.getFullYear() + 1);
signedCert.setSubject(csr.subject.attributes);
signedCert.setIssuer(conf.serverConfig.caCert.subject.attributes);
signedCert.setExtensions([
{
name : 'keyUsage',
digitalSignature : true,
keyEncipherment : true,
}
]);
signedCert.publicKey = csr.publicKey;
signedCert.sign(conf.serverConfig.caPrivateKey);
const degenerate = forge.pkcs7.createSignedData();
degenerate.addCertificate(signedCert);
const enveloped = forge.pkcs7.createEnvelopedData();
// UPDATE 1
enveloped.recipients.push({
version: 0,
issuer: csr.subject.attributes,
serialNumber: signedCert.serialNumber,
encryptedContent: {
algorithm: forge.pki.oids.rsaEncryption,
key: csr.publicKey
}
});
enveloped.content = forge.asn1.toDer(degenerate.toAsn1());
enveloped.encryptedContent.algorithm = forge.pki.oids['des-EDE3-CBC'];
enveloped.encrypt();
const signed = forge.pkcs7.createSignedData();
signed.addCertificate(conf.serverConfig.caCert);
signed.addSigner({
key : conf.serverConfig.caPrivateKey,
certificate : conf.serverConfig.caCert,
digestAlgorithm : forge.pki.oids.sha1,
authenticatedAttributes : [
{
type : forge.pki.oids.contentType,
value : forge.pki.oids.data
},
{
type: forge.pki.oids.messageDigest
},
{
type: forge.pki.oids.signingTime,
},
]
});
signed.content = forge.asn1.toDer(enveloped.toAsn1());
signed.sign();
const signedDer = new Buffer(forge.asn1.toDer(signed.toAsn1()).getBytes(), 'binary');
return reply(signedDer).bytes(signedDer.length).type('application/x-pki-message');
} catch(e) {
req.log( ['error' ], { message : e.toString() } );
return reply('The CA could not validate the request').code(403);
}
}
Кто-нибудь может указать, что я здесь делаю не так?
Обновление 1: обновленный код выше отражает мои последние. Все еще не работает, но я считаю, что информация о получателе теперь верна. (Увидеть UPDATE 1
выше)
1 ответ
Наконец-то все заработало (и к следующей головной боли, связанной со SCEP!):
Обзор проблем в оригинальном коде:
- Получатель ДОЛЖЕН быть сертификатом, подписавшим запрос:
p7Message.certificates[0]
- SCEP определяет некоторые аутентифицированные атрибуты, которые ДОЛЖНЫ присутствовать. Из тех,
transactionID
а такжеsenderNonce
Исходный запрос должен быть отправлен обратно (senderNonce
отправляется обратно какrecipientNonce
) - OID для определенных атрибутов SCEP в настоящее время не поддерживаются
node-forge
, Это потребовало очень простого взлома (см. Подписанные данные PKCS#7 и пользовательские идентифицированные атрибуты / OID)
Обновленный рабочий код: ниже приведен обновленный и рабочий код (обратите внимание, что все еще есть некоторые пропущенные проверки, которые необходимо реализовать для проверки / и т. Д.)
function pkiOperationScepOperationHandler(req, reply) {
//
// |req.query.message| should contain a Base64 encoded PKCS#7 package.
// The SignedData portion is PKCS#7 EnvelopedData encrypted with the CA
// public key we gave the client in GetCACert. Once decrypted, we have
// ourselves the client's CSR.
//
if(!req.query.message) {
return reply('The CA could not validate the request').code(403);
}
try {
const msgBuffer = new Buffer(req.query.message, 'base64');
const p7Message = forge.pkcs7.messageFromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(msgBuffer, 'binary')
)
);
// :TODO: Validate integrity
// :TODO: Validated signing
//
// The outter PKCS#7 signed data must contain authenticated
// attributes for transactionID and senderNonce. We will use these
// in our reply back as part of the SCEP spec.
//
const oids = forge.pki.oids;
let origTransactionId = p7Message.rawCapture.authenticatedAttributes.find( attr => {
const oid = forge.asn1.derToOid(attr.value[0].value);
return ('2.16.840.1.113733.1.9.7' === oid); // transactionID
});
if(!origTransactionId) {
return reply('Invalid request payload').code(403);
}
origTransactionId = origTransactionId.value[1].value[0].value; // PrintableString
let origSenderNonce = p7Message.rawCapture.authenticatedAttributes.find( attr => {
const oid = forge.asn1.derToOid(attr.value[0].value);
return ('2.16.840.1.113733.1.9.5' === oid); // senderNonce
});
if(!origSenderNonce) {
return reply('Invalid request payload').code(403);
}
origSenderNonce = origSenderNonce.value[1].value[0].value; // OctetString
const p7EnvelopedData = forge.pkcs7.messageFromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(new Buffer(p7Message.rawCapture.content.value[0].value[0].value, 'binary'), 'binary')
)
);
// decrypt using our key
p7EnvelopedData.decrypt(p7EnvelopedData.recipients[0], conf.serverConfig.caPrivateKey);
// p7EnvelopedData should contain a PKCS#10 CSR
const csrDataBuffer = new Buffer(p7EnvelopedData.content.getBytes(), 'binary');
const csr = forge.pki.certificationRequestFromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(csrDataBuffer, 'binary')
),
true // computeHash
);
//
// Create a new cert based on the CSR and sign it
//
// See https://github.com/digitalbazaar/forge/issues/154
//
const signedCert = forge.pki.createCertificate();
signedCert.serialNumber = Date.now().toString();
signedCert.validity.notBefore = new Date();
signedCert.validity.notAfter = new Date();
// expires one year from now (client should contact us before then to renew)
signedCert.validity.notAfter.setFullYear(signedCert.validity.notBefore.getFullYear() + 1);
signedCert.setSubject(csr.subject.attributes);
signedCert.setIssuer(conf.serverConfig.caCert.subject.attributes);
// :TODO: Really, this should come from requested extensions in the CSR
signedCert.setExtensions([
{
name : 'keyUsage',
digitalSignature : true,
keyEncipherment : true,
critical : true,
}
]);
signedCert.publicKey = csr.publicKey;
signedCert.sign(conf.serverConfig.caPrivateKey);
req.log( ['trace' ], { message : 'Signed CSR certificate', cert : forge.pki.certificateToPem(signedCert) } );
const degenerate = forge.pkcs7.createSignedData();
degenerate.addCertificate(signedCert);
degenerate.sign();
const enveloped = forge.pkcs7.createEnvelopedData();
// Recipient is the original requester cert
enveloped.addRecipient(p7Message.certificates[0]);
enveloped.content = forge.asn1.toDer(degenerate.toAsn1());
enveloped.encryptedContent.algorithm = forge.pki.oids['des-EDE3-CBC']; // We set this in GetCACaps
enveloped.encrypt();
// Package up everything in PKCS#7 signed (by us) data
const signed = forge.pkcs7.createSignedData();
signed.addSigner({
key : conf.serverConfig.caPrivateKey,
certificate : conf.serverConfig.caCert,
digestAlgorithm : forge.pki.oids.sha1,
authenticatedAttributes : [
{
type : forge.pki.oids.contentType,
value : forge.pki.oids.data
},
{
type: forge.pki.oids.messageDigest
},
{
type: forge.pki.oids.signingTime,
},
{
name : 'transactionID',
type : '2.16.840.1.113733.1.9.7',
rawValue : forge.asn1.create(
forge.asn1.Class.UNIVERSAL,
forge.asn1.Type.PRINTABLESTRING,
false,
origTransactionId
),
},
{
name : 'messageType',
type : '2.16.840.1.113733.1.9.2',
rawValue : forge.asn1.create(
forge.asn1.Class.UNIVERSAL,
forge.asn1.Type.PRINTABLESTRING,
false,
'3' // CertRep
),
},
{
name : 'senderNonce',
type : '2.16.840.1.113733.1.9.5',
rawValue : forge.asn1.create(
forge.asn1.Class.UNIVERSAL,
forge.asn1.Type.OCTETSTRING,
false,
forge.util.createBuffer(forge.random.getBytes(16)).bytes()
),
},
{
name : 'recipientNonce',
type : '2.16.840.1.113733.1.9.6',
rawValue : forge.asn1.create(
forge.asn1.Class.UNIVERSAL,
forge.asn1.Type.OCTETSTRING,
false,
origSenderNonce),
},
{
name : 'pkiStatus',
type : '2.16.840.1.113733.1.9.3',
rawValue : forge.asn1.create(
forge.asn1.Class.UNIVERSAL,
forge.asn1.Type.PRINTABLESTRING,
false,
'0' // SUCCESS
),
}
]
});
signed.content = forge.asn1.toDer(enveloped.toAsn1());
signed.sign();
const signedDer = new Buffer(forge.asn1.toDer(signed.toAsn1()).getBytes(), 'binary');
return reply(signedDer).bytes(signedDer.length).type('application/x-pki-message');
} catch(e) {
req.log( ['error' ], { message : e.toString() } );
return reply('The CA could not validate the request').code(403);
}
}
Это заняло несколько дней, чтобы получить права. Надеюсь, это может кому-то помочь!