Gerar uma chave e um arquivo PKCS #12 - PKI Express
O PKI Express pode gerar um par de chaves RSA no seu servidor, com uma requisição de assinatura de certificado (CSR) para
ele, e, depois que uma AC emitir o certificado, juntar a chave e o certificado num arquivo PKCS #12 (.pfx) com o qual você pode
assinar. Duas classes fazem isso: KeyGenerator, que executa o comando
gen-key, e Pkcs12Generator, que executa o create-pfx.
- Os dois comandos exigem o PKI Express 1.11.0 ou posterior. As bibliotecas cliente pedem essa versão; com um PKI Express mais antigo, a operação falha com uma dica que informa a versão (veja Códigos de retorno).
- As bibliotecas de Java, Node.js, PHP e Python têm essas classes; a de Ruby (
pki_express1.3.2) não tem. - Os samples do PKI Suite as usam para emitir um certificado com o Amplia e depois assinar um PDF com ele (Issue a certificate with the key on the server e Sign a PDF with a server key).
Fluxo
KeyGenerator.generate(), comgenCsrdefinido, devolve a chave privada e um CSR.- Guarde a chave no seu servidor e envie o CSR à sua AC, como o Amplia, que devolve o certificado.
Pkcs12Generator.generate()recebe a chave e o arquivo do certificado e devolve o arquivo PKCS #12, protegido pela senha que você informar.
Em PHP, com o pacote lacuna/pki-express (1.17.0):
use Lacuna\PkiExpress\KeyGenerator;
use Lacuna\PkiExpress\KeyFormats;
use Lacuna\PkiExpress\Pkcs12Generator;
use Lacuna\PkiExpress\SupportedKeySizes;
// 1. Make the key pair and the CSR.
$keyGenerator = new KeyGenerator();
$keyGenerator->keySize = SupportedKeySizes::S2048;
$keyGenerator->keyFormat = KeyFormats::JSON;
$keyGenerator->genCsr = true;
$genKey = $keyGenerator->generate();
// $genKey->key: the private key, as a string in the chosen format
// $genKey->csr: the PKCS #10 CSR, PEM-encoded
// 2. Store $genKey->key (encrypted) and send $genKey->csr to the CA,
// which returns the certificate, saved here as $certPath.
// 3. Make the PKCS #12 file.
$pkcs12Generator = new Pkcs12Generator();
$pkcs12Generator->key = $genKey->key;
$pkcs12Generator->setCertFile($certPath);
$result = $pkcs12Generator->generate($password);
$result->pfx->writeToFile('/path/to/signer.pfx'); // or getContentRaw(), getContentBase64()
Os samples chamam Util::setPkiDefaults() nos dois objetos, como em todo operador do PKI Express, e guardam a chave e o certificado
entre os passos 1 e 3.
Eles alertam que a chave privada deve ser criptografada antes de ser guardada.
Opções da geração de chave
| Opção | PHP | Valores | Padrão |
|---|---|---|---|
| Tamanho da chave, em bits | keySize | SupportedKeySizes::S1024, S2048, S4096 (1024, 2048, 4096) | 2048 |
| Formato da chave | keyFormat, ou o argumento de generate() | KeyFormats::JSON (json), KeyFormats::BLOB (blob), KeyFormats::XML (xml) | json |
| CSR | genCsr | true para gerar um CSR; deixe sem valor para gerar só a chave | sem CSR |
- A chave é RSA. Só 1024, 2048 e 4096 bits são aceitos: as bibliotecas cliente recusam outro tamanho antes de executar o PKI Express (o
PHP lança uma
RuntimeException), e o próprio PKI Express devolve36 - KeySizeNotSupported. - Os três formatos guardam a mesma chave privada, serializada de formas diferentes:
jsoné um objeto JSON com os parâmetros RSA,xmlum documento<RSAKeyValue>eblobum blob de chave CSP em Base64. Os samples usamjson. - O CSR é uma requisição PKCS #10, codificada em PEM e assinada com SHA-256 com RSA; ele leva só a chave pública, e por isso o subject do certificado vem da AC (no Amplia, do pedido).
generate()devolve umKeyGenerationResultcom duas strings:keyecsr(nullsemgenCsr).
Opções do PKCS #12
| Entrada | PHP |
|---|---|
A chave, como o KeyGenerator a devolveu, em qualquer dos três formatos | propriedade key (ou o primeiro argumento do construtor) |
| O arquivo do certificado | setCertFile($path) (alias de setCertFileFromPath), setCertFileFromContentRaw($bytes), setCertFileFromContentBase64($base64) |
| A senha do arquivo PKCS #12 | argumento de generate($password), ou a propriedade password |
- O PKI Express reconhece o formato da chave pelo conteúdo; uma chave que ele não consegue ler dá
37 - BadKeyFormatou38 - BadKey. - Sem senha, o arquivo PKCS #12 é criado com uma senha vazia.
generate()devolve umPkcs12GenerationResultcujopfxé umPkcs12Certificate:getContentRaw()dá os bytes,getContentBase64()o Base64, ewriteToFile($path)grava o arquivo. Para assinar com ele, passe os bytes asetPkcs12Content()e a senha asetCertPassword(), como faz o sample de assinatura PAdES com chave no servidor.- A biblioteca cliente passa a chave e a senha ao
pkiecomo argumentos de linha de comando. - Se você preferir que a AC gere a chave, o Amplia pode devolver o próprio arquivo PKCS #12: veja Emitir um arquivo PKCS #12.
Em cada linguagem
As classes têm o mesmo nome em todas as bibliotecas que as têm; os setters e os objetos de resultado mudam:
| Biblioteca | Opções da chave | Resultado da chave | Certificado e senha | Resultado do PKCS #12 |
|---|---|---|---|---|
| PHP | keySize, keyFormat, genCsr | $r->key, $r->csr | key, setCertFile($path), generate($password) | $r->pfx->getContentRaw(), writeToFile($path) |
| Java | setKeySize(int), setKeyFormat(KeyFormats), setGenCsr(boolean) | getKey(), getCsr() | setKey, setCertFile(path) (também byte[], InputStream), generate(password) | getPfxContent() (byte[]), getPfxContentBase64() |
| Node.js | construtor { keySize, keyFormat, genCsr }, ou os setters | key, csr | construtor { key }, setCertFileFromPathSync(path), generate(password) | pfx.contentRaw (Buffer), pfx.contentBase64, pfx.writeToFileSync(path) |
| Python | key_size, key_format, gen_csr | key, csr | key, set_cert_file_from_path(path), generate(password) | pfx.content (bytes), pfx.write_to_file(path) |
- Java, Node.js e Python usam por padrão uma chave de 2048 bits no formato
json; o PHP não envia nenhuma opção, e o PKI Express aplica os mesmos padrões. - Em Python, defina
key_sizecom as constantes deSupportedKeySizes, que são strings ('2048'); em Node.js,keySizeaceita2048ou'2048'. - Todas as bibliotecas exportam
KeyFormats;SupportedKeySizes, só PHP e Python. - Em Node.js,
generate()devolve uma promise.
- Java
- Node.js
- Python
KeyGenerator keyGenerator = new KeyGenerator();
keyGenerator.setKeySize(2048);
keyGenerator.setKeyFormat(KeyFormats.JSON);
keyGenerator.setGenCsr(true);
KeyGenerationResult genKey = keyGenerator.generate();
// genKey.getKey(), genKey.getCsr()
Pkcs12Generator pkcs12Generator = new Pkcs12Generator();
pkcs12Generator.setKey(keyJson); // the key from genKey.getKey(), read back from storage
pkcs12Generator.setCertFile(certPath);
Pkcs12GenerationResult pkcs12GenResult = pkcs12Generator.generate(password);
signer.setPkcs12(pkcs12GenResult.getPfxContent());
signer.setCertPassword(password);
const { KeyGenerator, KeyFormats, Pkcs12Generator } = require('pki-express');
const keyGenerator = new KeyGenerator({ keySize: 2048, keyFormat: KeyFormats.JSON, genCsr: true });
const genKey = await keyGenerator.generate();
// genKey.key, genKey.csr
const pkcs12Generator = new Pkcs12Generator({ key: keyJson }); // the key from genKey.key, read back from storage
pkcs12Generator.setCertFileFromPathSync(certPath);
const pkcs12GenResult = await pkcs12Generator.generate(password);
await signer.setPkcs12FromRaw(pkcs12GenResult.pfx.contentRaw);
signer.certPassword = password;
from pkiexpress import KeyGenerator, Pkcs12Generator, SupportedKeySizes
key_generator = KeyGenerator()
key_generator.key_size = SupportedKeySizes.S2048
key_generator.gen_csr = True
gen_key = key_generator.generate()
# gen_key.key, gen_key.csr
pkcs12_generator = Pkcs12Generator()
pkcs12_generator.key = gen_key.key
pkcs12_generator.set_cert_file_from_base64(cert.content_base64)
pkcs12_gen_result = pkcs12_generator.generate('1234')
pkcs12_gen_result.pfx.write_to_file('signer.pfx')
Erros
Um comando que falha dá um dos códigos de retorno do PKI Express: 36 - KeySizeNotSupported, 37 - BadKeyFormat ou
38 - BadKey nesses comandos, e 04 - BadCertificate quando não é possível decodificar o arquivo do certificado. Em PHP, trate a
CommandException e leia $e->returnCode: veja Códigos de retorno nas bibliotecas cliente.
TODO(docs): se o create-pfx verifica se o certificado corresponde à chave, e qual código de retorno ele dá quando não corresponde.