I was looking at how the password was getting converted to bytes today.
The PKCS12 spec (downloaded from 
http://www.rsa.com/rsalabs/node.asp?id=2138, page 14) says the password
bytes are BMPStrings with a null terminator and no byte order marks. It 
shows a sample password of "Beavis" and the bytes (in hex) that should 
be produced from that password:
00 42 00 65 00 61 00 76 00 69 00 73 00 00

Both the JSS and BC BMPString classes produce correct output when given
"Beavis\0" (ignoring they have the byte order marks at the front which
could be removed fairly easily).
The PasswordConverter class in PKCS12 package does the proper thing
completely on its own if passed in "Beavis". It removes the BOM and adds 
the NULL at the end and everything is 2 bytes per char.
The 4th is basically just doing the same thing that the code inside the
PWConverter class (code below) does.

JSS bmpstring password bytes       : 1e0e0042006500610076006900730000
BC bmpstring password bytes        : 1e0e0042006500610076006900730000
JSS pw converter password bytes    : 0042006500610076006900730000
Java password bytes                : 4265617669730000
custom password bytes              : 4265617669730000
Jss password object charToByte     : 42656176697300

What's weird is that the last line of output is the thing that will
successfully decrypt the epki bytes passed back from NSS and that 
doesn't make
sense at all because it's not 2 bytes per char, but it does have 2 bytes 
for
the null at the end. (I found out why below...)

- When getEncryptedPrivateKeyInfo is called you pass in a 
org.mozilla.jss.util.Password.  This gets passed down to NSS which calls 
back to the java object and runs the non-public method getByteCopy() 
(http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs11/PK11Store.c#532).
 

- getByteCopy calls charToByte which is hardcoded to use a 
null-terminated UTF8 (which only adds 1 of the null bytes so at this 
point we end up with 42656176697300 after inputting "Beavis" as the 
password) 
(http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/util/Password.java#216)
- The native nss code then goes on to add the second 1 byte null at the 
end in a somewhat shady way 
(http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs11/PK11Store.c#534).
 
  It just adds one to the length.  I'm not 100% on NSS coding 
conventions (or C for that matter), but I don't see anywhere in there 
where the memory for the SECItem holding the password is zeroed so is 
just adding 1 to the length ok?

So if the Password object was created with the password "Beavis" the 
bytes that NSS would use for creating the epki would be 4265617669730000.

I'm pretty sure this is where the incompatiblities are coming from.  The 
only reason the PKCS12 is usable after JSS builds it is that JSS itself 
re-encrypts the key by calling SafeBag.createEncryptedPrivateKeyBag, you 
pass in a password object and when it creates the epki internally it 
passes in an instance of PasswordConverter from the pkcs12 package 
(http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs12/SafeBag.java#307)
 
(which, as demonstrated above, produces bytes according to the PKCS12 
spec) so the java/jss-built EPKI is encrypted according to spec).  The 
same goes for AuthenticatedSafe.addEncryptedSafeContents, it also uses a 
"PasswordConverter" instance 
(http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs12/AuthenticatedSafes.java#354).
 
  This is why the shady home-grown KeyGenerator.CharToByteConverter is 
needed for decrypting the CryptoStore-provided epki(NSS at a lower 
level) which doesn't act according to the PKCS12 spec.  This is why 
openssl and Java cannot decrypt these EPKI structures, they're deriving 
the wrong key given the same password text.

So it seems the Password class needs a way to specify what "char to 
byte" conversion method should be used as null-terminated UTF8 is not 
the correct way to do it in all cases.  That or maybe provide a way to 
pass the converted bytes themselves in to the NSS-native method 
Java_org_mozilla_jss_pkcs11_PK11Store_getEncryptedPrivateKeyInfo? 
(http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs11/PK11Store.c#482).

The code to produce the above samples (some BouncyCastle classes):

           Hex hex = new Hex();
           char[] passChars = new char[]{'B','e','a','v','i','s','\0'};
           String passString = "Beavis\0";

           //JSS BMPString
           BMPString testString = new BMPString(passChars);
           byte[] passBytes = ASN1Util.encode(testString);
           System.out.println("JSS bmpstring password bytes       : " + new
String(hex.encode(passBytes)));

           //BC bmpstring
           org.bouncycastle.asn1.DERBMPString bcBMPString = new
org.bouncycastle.asn1.DERBMPString(passString);
           byte[] bcPassBytes = bcBMPString.getDEREncoded();
           System.out.println("BC bmpstring password bytes        : " + new
String(hex.encode(bcPassBytes)));

           //JSS pkcs12.PasswordConverter
           PasswordConverter pc = new PasswordConverter();
           byte[] jssPWbytes = pc.convert("Beavis".toCharArray());
           System.out.println("JSS pw converter password bytes    : " + new
String(hex.encode(jssPWbytes)));

           //these 2 match output
           byte[] javaPassBytes = (passString + '\0').getBytes("UTF-8");
           System.out.println("Java password bytes                : " + new
String(hex.encode(javaPassBytes)));

           PWConverter converter = new PWConverter();
           byte[] customPassBytes = converter.convert(passChars);
           System.out.println("custom password bytes              : " + new
String(hex.encode(customPassBytes)));

           byte[] jssPasswordBytes = 
password.charToByte("Beavis".toCharArray());
           System.out.println("Jss password object charToByte     : " + 
new String(hex.encode(jssPasswordBytes)));

Our PWConverter:
   class PWConverter implements KeyGenerator.CharToByteConverter {
     public byte[] convert(char[] chars) {
       try {
         String s = new String(chars);
         byte[] raw = s.getBytes("UTF-8");
         byte[] cooked = new byte[raw.length + 1];
         System.arraycopy(raw, 0, cooked, 0, raw.length);
         return cooked;
       }
       catch (Exception e) {
         return null;
       }
     }
   }

Dave
_______________________________________________
dev-tech-crypto mailing list
dev-tech-crypto@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-tech-crypto

Reply via email to