A brief context: in mexico there is the so called "electronic invoice". The IRS equivalent, emits a certificate/key pair (.cer, .key) in DER format, named Digital Sign Certificate to each tax payer. This certificate is used to sign an horrible XML file that is in fact, the invoice. The private key is PCKS8 with des-ede3-cbc encryption.
Invoicing software use this certificate to sign the XML file and send it (typically invoking a web service) to a so called PAC to add a second signature.
We created a system to do such invoicing using Delphi 10 and OpenSLL (libeay32.dll and ssleay32.dll) The user uploads his/her .cer,.key pair and the program converts to PEM format and stores in a database. This part is important: after that, the program no longer needs (or needed) the original pair and does not have access to it.
Maybe is irrelevant, but this is the code to load the .cer (x509) file and convert it to PEM.
Var
infile,
outfile : pBIO;
x : pX509;
n : Integer;
PEM,
fn : AnsiString; // holds the filename
BIO_read_filename(infile,PansiChar(fn));
x := d2i_X509_bio(infile,Nil);
outfile := BIO_new(BIO_s_mem);
PEM_write_bio_X509(outfile,x);
n := BIO_pending(outfile);
SetLength(PEM,n);
BIO_read(outfile,@PEM[1],n) // PEM is saved to the database
The code to load .key:
Var
infile,
outfile : pBIO;
p8 : pX509_SIG;
p8inf : pPKCS8_Priv_Key_Info;
n : Integer;
x : pEVP_PKEY;
PEM,
fn : AnsiString;
infile := BIO_new(BIO_s_file);
BIO_read_filename(infile,PANSICHAR(fn));
p8 := d2i_PKCS8_bio(infile,Nil);
p8inf := PKCS8_decrypt(p8,_PCHAR(Password),Length(Password)); // Password is received as parameter
x := EVP_PKCS82PKEY(p8inf);
outfile := BIO_new(BIO_s_mem);
PEM_write_bio_PKCS8PrivateKey(outfile,x,Nil,_PCHAR(Password),Length(Password),Nil,Nil);
n := BIO_pending(outfile);
SetLength(PEM,n);
BIO_read(outfile,@PEM[1],n) // PEM is saved to the database
Due to new requirements, a call to the web service needs the original .cer/.key pair. This didn't seem to be a problem: it could be reconstructed from the PEM stored in the database, so the following code was written (only shown for the private key. The x509 code produces an exact copy of the original):
Var
infile,
outfile : pBIO;
n : Integer;
pRSAKey : pEVP_PKEY;
DER : TArray<Byte>;
infile := BIO_new(BIO_s_mem);
BIO_puts(infile,PAnsiChar(PEM_key_saved));
PEM_read_bio_PrivateKey(infile,pRSAKey,Nil,PAnsiChar(Password));
outfile := BIO_new(BIO_s_mem);
i2d_PKCS8PrivateKey_bio(outfile,pRSAKey,EVP_des_ede3_cbc(),Nil,0,Nil,PAnsiChar(Password)
n := BIO_pending(outfile);
SetLength(DER,n);
BIO_read(outfile,@DER[0],n) // DER should have a byte to byte copy of the original .key
At this point, we expected DER to have an exact copy of the original (let's call it) original.key file, but it is not. The contents of DER were saved to new.key. This is what happened:
- Both original.key and new.key are the same size but contents are distinct
- The command openssl pkcs8 -in (file).key -inform der outputs exactly the same "-----BEGIN RSA PRIVATE KEY-----..." text
- The command openssl asn1parse -inform der -in (file).key outputs distinct results for each file
- The web service rejects new.key but if original.key is sent, the call succeeds.
We ran on both files:
openssl asn1parse -inform der -in (file).key
The results:
original.key
0:d=0 hl=4 l=1294 cons: SEQUENCE
4:d=1 hl=2 l= 64 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 51 cons: SEQUENCE
19:d=3 hl=2 l= 27 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 14 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:0201000282010100
44:d=5 hl=2 l= 2 prim: INTEGER :0800
48:d=3 hl=2 l= 20 cons: SEQUENCE
50:d=4 hl=2 l= 8 prim: OBJECT :des-ede3-cbc
60:d=4 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:308204BD02010030
70:d=1 hl=4 l=1224 prim: OCTET STRING [HEX DUMP]:D2268F527FDA1E5B97...
new.key
0:d=0 hl=4 l=1294 cons: SEQUENCE
4:d=1 hl=2 l= 64 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 51 cons: SEQUENCE
19:d=3 hl=2 l= 27 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 14 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:22001506BC2D25ED
44:d=5 hl=2 l= 2 prim: INTEGER :0800
48:d=3 hl=2 l= 20 cons: SEQUENCE
50:d=4 hl=2 l= 8 prim: OBJECT :des-ede3-cbc
60:d=4 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:ADBF5AE2FA728F54
70:d=1 hl=4 l=1224 prim: OCTET STRING [HEX DUMP]:EC2876939E0CC1A1...
As noted, the are a differences, specially in the last OCTECT STRING that for some reason, makes the web service fail (it's from an external company so we can not modify it in any way) despite containing the same private key
The question is: are we missing something during the PEM to DER conversion? Should some fields in pRSAKey need to be set? As mentioned, the code for reconstructing the .cer file works fine and produces and exact copy of the original.
BIO_read(outfile,@DER[0],n)