iOS MDM SCEP PKI操作:SCEP服务器返回无效响应

iOS MDM SCEP PKI操作:SCEP服务器返回无效响应,ios,node.js,mdm,scep,Ios,Node.js,Mdm,Scep,前言 我正在Node.js中实现一个iOS MDM服务器,并将其用于PKI。部分设备注册需要使用SCEP 问题 设备当前在对我的服务器的初始operation=PKIOperationCSR请求中失败。从设备上看到的错误信息相当模糊: May 18 14:39:46 iPad-2 Preferences[27999] <Notice>: (Error) MC: Install profile data, interactive error. Error: NSError: D

前言

我正在Node.js中实现一个iOS MDM服务器,并将其用于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;
    }
最后,这里是我使用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: 更新了上面的代码以反映我的最新信息。仍然不起作用,但我相信收件人的信息现在是正确的。(请参见上面的更新1)

最终实现了这一点(并进入下一个与SCEP相关的头痛问题!):

原始代码中的问题概述

  • 收件人必须是签署请求的证书:
    p7Message.certificates[0]
  • SCEP定义了一些必须存在的经过身份验证的属性。其中,必须发回原始请求中的
    transactionID
    SenderOnce
    SenderOnce
    作为
    recipientNonce
    发回)
  • node forge
    当前不支持SCEP特定属性的OID。这需要一个非常简单的破解(请参阅)
更新的工作代码: 下面是一些更新的工作代码(请注意,仍有一些缺少的检查需要进行验证等)

这花了几天时间才恢复正常。希望它能对某人有所帮助

终于成功了(接着是下一个与SCEP相关的头痛问题!):

原始代码中的问题概述

  • 收件人必须是签署请求的证书:
    p7Message.certificates[0]
  • SCEP定义了一些必须存在的经过身份验证的属性。其中,必须发回原始请求中的
    transactionID
    SenderOnce
    SenderOnce
    作为
    recipientNonce
    发回)
  • node forge
    当前不支持SCEP特定属性的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);
    }

    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);
    }
}
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);
    }       
}