You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I imagine this is rather an edge case given that we are living in Czech republic and our language use specific letters (ÁÉĚÍÝÓÚŮŽŠČŘĎŤŇáéěíýóúůžščřďťň), but for us it was a huge blocker that required fast workaround and I would be glad if you could take a look at it.
We are using your package to extract data from .pfx/.p12 files and turns out that if friendlyName attribute contains any of the mentioned letters, the parsePkcs12 function cannot handle it and throws exception.
Following certificate (posting in bytes so you can reproduce it easily), contains letter "í" in the friendlyName: cert_in_bytes.txt
Password to decode it is: 123456789
When you pass it to parsePkcs12 function, it is gonna fail eventually in ASN1Sequence.fromBytes() constructor call with exception: Format exception, 237 character is invalid...according to extended ASCII table, 237 is equivalent of "í". That is how I found the issue. Initially I thought it will be problem if it appears anywhere inside a certificate (like in SUBJECT, ORGANIZATION etc.), but no, it is only a problem if it is in the friendlyName.
As this is represented under following OID
// OID 1.2.840.113549.1.9.20
// OID for friendlyName in hexadecimal: 06 09 2A 86 48 86 F7 0D 01 09 14
List<int> friendlyNameOid = [0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x14];
I am able to look for those bytes in the decryptes Uint8List and find and replace diacritics that follows after it.
Functions added to hex_utils.dart
/// Modifies the given [bytes] to replace Czech diacritics with their ASCII equivalents
static Uint8List _sanitizeCzechDiacritics(Uint8List bytes) {
Map<int, int> czechDiacriticsMap = {
// Uppercase letters
193: 65, // Á -> A
201: 69, // É -> E
204: 69, // Ě -> E
205: 73, // Í -> I
221: 89, // Ý -> Y
211: 79, // Ó -> O
218: 85, // Ú -> U
366: 85, // Ů -> U
381: 90, // Ž -> Z
352: 83, // Š -> S
268: 67, // Č -> C
344: 82, // Ř -> R
270: 68, // Ď -> D
356: 84, // Ť -> T
327: 78, // Ň -> N
// Lowercase letters
225: 97, // á -> a
233: 101, // é -> e
283: 101, // ě -> e
237: 105, // í -> i
253: 121, // ý -> y
243: 111, // ó -> o
250: 117, // ú -> u
367: 117, // ů -> u
382: 122, // ž -> z
353: 115, // š -> s
269: 99, // č -> c
345: 114, // ř -> r
271: 100, // ď -> d
357: 116, // ť -> t
328: 110, // ň -> n
};
var sanitizedContent = Uint8List.fromList(bytes);
for (int i = 0; i < sanitizedContent.length; i++) {
int byte = sanitizedContent[i];
// Check if the byte is in the map and replace it with the ASCII equivalent
if (czechDiacriticsMap.containsKey(byte)) {
final replacement = czechDiacriticsMap[byte]!;
print('Found Czech diacritic byte at index $i: $byte, it will be replaced with $replacement');
sanitizedContent[i] = replacement;
}
}
return sanitizedContent;
}
/// Finds and sanitizes the friendly name in the given [content] byte array
static Uint8List? findAndSanitizeFriendlyName(Uint8List content) {
// OID 1.2.840.113549.1.9.20
// OID for friendlyName in hexadecimal: 06 09 2A 86 48 86 F7 0D 01 09 14
List<int> friendlyNameOid = [0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x14];
// Scan through the content byte array to locate the OID
for (int i = 0; i <= content.length - friendlyNameOid.length; i++) {
bool match = true;
for (int j = 0; j < friendlyNameOid.length; j++) {
if (content[i + j] != friendlyNameOid[j]) {
match = false;
break;
}
}
// If the OID matches, process the friendlyName
if (match) {
print('Found friendlyName OID at index $i');
// After finding the OID, the next bytes represent the length and content of the BMPString
int lengthIndex = i + friendlyNameOid.length + 3; // Length comes after OID
int length = content[lengthIndex]; // Assuming one-byte length for simplicity
// Extract the BMPString (two-byte character encoding)
int startIndex = lengthIndex + 1; // Start reading after the length byte
int endIndex = startIndex + length;
Uint8List friendlyNameBytes = content.sublist(startIndex, endIndex); // Extract the friendly name bytes
// Sanitize the friendly name bytes
final sanitized = _sanitizeCzechDiacritics(friendlyNameBytes);
// Replace the original friendly name bytes with the sanitized bytes
for (int j = 0; j < sanitized.length; j++) {
content[startIndex + j] = sanitized[j];
}
// Convert to loggable string, the replace take care of SUB, NUL, ACK chars
String originalFriendlyNameString =
String.fromCharCodes(friendlyNameBytes).replaceAll(RegExp(r'[\u0000-\u001F\u007F]'), '');
String convertedFriendlyNameString =
String.fromCharCodes(sanitized).replaceAll(RegExp(r'[\u0000-\u001F\u007F]'), '');
print('Original Friendly Name: $originalFriendlyNameString');
print('Sanitized Friendly Name: $convertedFriendlyNameString');
// Return the modified content with the sanitized friendly name
return content;
}
}
return null;
}
Usage in parsePkcs12.dart (look for function findAndSanitizeFriendlyName)
static List<String> parsePkcs12(
Uint8List pkcs12, {
String? password,
}) {
Uint8List? pwFormatted;
if (password != null) {
pwFormatted = formatPkcs12Password(Uint8List.fromList(password.codeUnits));
}
var pems = <String>[];
var parser = ASN1Parser(pkcs12);
var wrapperSeq = parser.nextObject() as ASN1Sequence;
var pfx = ASN1Pfx.fromSequence(wrapperSeq);
var authSafeContent = pfx.authSafe.content as ASN1OctetString;
parser = ASN1Parser(authSafeContent.valueBytes);
wrapperSeq = parser.nextObject() as ASN1Sequence;
if (wrapperSeq.elements == null || wrapperSeq.elements!.isEmpty) {
// TODO
}
for (var e in wrapperSeq.elements!) {
if (e is ASN1Sequence) {
if (e.elements == null || e.elements!.isEmpty) {
// TODO
}
var contentInfo = ASN1ContentInfo.fromSequence(e);
switch (contentInfo.contentType.objectIdentifierAsString) {
case '1.2.840.113549.1.7.6': // encryptedData
var encryptedData = ASN1EncryptedData.fromSequence(contentInfo.content as ASN1Sequence);
var encryptedContentInfo = encryptedData.encryptedContentInfo;
// GET ALGORITHM
var contentEncryptionAlgorithm = encryptedContentInfo.contentEncryptionAlgorithm;
var encryptionAlgorithm = _algorithmFromOi(contentEncryptionAlgorithm.algorithm.objectIdentifierAsString!);
// GET SALT AND MACITER AND DIGEST ALGORITHM
Uint8List salt = _getSaltFromAlgorithmParameters(contentEncryptionAlgorithm.parameters);
int macIter = _getMacIterFromAlgorithmParameters(contentEncryptionAlgorithm.parameters);
var digestAlgorithm = _getDigestAlgorithmFromEncryptionAlgorithm(encryptionAlgorithm);
// DECRYPT
var decryptedContent = _decrypt(
encryptedContentInfo.encryptedContent!,
encryptionAlgorithm,
pwFormatted!,
salt,
macIter,
digestAlgorithm,
);
var contentType = encryptedContentInfo.contentType;
final friendlyNameSanitized = HexUtils.findAndSanitizeFriendlyName(decryptedContent);
switch (contentType.objectIdentifierAsString) {
case '1.2.840.113549.1.7.1': // CERTIFICATES
var safeContents = ASN1SafeContents.fromSequence(
ASN1Sequence.fromBytes(friendlyNameSanitized ?? decryptedContent),
);
safeContents.safeBags.forEach((element) {
var pem = _pemFromSafeBag(element);
pems.add(pem);
});
break;
}
print('');
break;
case '1.2.840.113549.1.7.1': // data (PKCS #7)
final friendlyNameSanitized = HexUtils.findAndSanitizeFriendlyName(contentInfo.content!.valueBytes!);
var safeContents = ASN1SafeContents.fromSequence(
ASN1Sequence.fromBytes(friendlyNameSanitized ?? contentInfo.content!.valueBytes!),
);
safeContents.safeBags.forEach((element) {
var bagValueSeq = element.bagValue as ASN1Sequence;
switch (element.bagId.objectIdentifierAsString!) {
case "1.2.840.113549.1.12.10.1.3": // pkcs-12-certBag
var seq = element.bagValue as ASN1Sequence;
var octet = ASN1OctetString.fromBytes(seq.elements!.elementAt(1).valueBytes!);
var asn1o = ASN1Sequence.fromBytes(octet.valueBytes!);
pems.insert(
0,
X509Utils.encodeASN1ObjectToPem(
asn1o,
X509Utils.BEGIN_CERT,
X509Utils.END_CERT,
),
);
break;
case "1.2.840.113549.1.12.10.1.2": // pkcs-12-pkcs-8ShroudedKeyBag
var contentEncryptionAlgorithm =
ASN1AlgorithmIdentifier.fromSequence(bagValueSeq.elements!.elementAt(0) as ASN1Sequence);
// GET ALGORITHM
var encryptionAlgorithm =
_algorithmFromOi(contentEncryptionAlgorithm.algorithm.objectIdentifierAsString!);
// GET SALT AND MACITER AND DIGEST ALGORITHM
Uint8List salt = _getSaltFromAlgorithmParameters(contentEncryptionAlgorithm.parameters);
int macIter = _getMacIterFromAlgorithmParameters(contentEncryptionAlgorithm.parameters);
var digestAlgorithm = _getDigestAlgorithmFromEncryptionAlgorithm(encryptionAlgorithm);
// DECRYPT
var decryptedContent = _decrypt(
bagValueSeq.elements!.elementAt(1).valueBytes!,
encryptionAlgorithm,
pwFormatted!,
salt,
macIter,
digestAlgorithm,
);
var s = ASN1Sequence.fromBytes(decryptedContent);
pems.insert(
0,
X509Utils.encodeASN1ObjectToPem(s, CryptoUtils.BEGIN_PRIVATE_KEY, CryptoUtils.END_PRIVATE_KEY),
); // TODO ECC ?
break;
case "1.2.840.113549.1.12.10.1.1": // pkcs-12-keyBag
var seq = bagValueSeq.elements!.elementAt(1) as ASN1Sequence;
var identifier = seq.elements!.elementAt(0) as ASN1ObjectIdentifier;
switch (identifier.objectIdentifierAsString!) {
case "1.2.840.113549.1.1.1": // rsaEncryption
pems.insert(
0,
X509Utils.encodeASN1ObjectToPem(
bagValueSeq, CryptoUtils.BEGIN_PRIVATE_KEY, CryptoUtils.END_PRIVATE_KEY),
);
break;
}
break;
}
});
break;
}
}
}
return pems;
}
Therefore to reproduce it on the current version of basic utils, you can open any project and just call:
final certBytes = [COPY THE ATTACHED LIST];
final extractedPems = Pkcs12Utils.parsePkcs12(certBytes, password: '123456789');
You should get the Format Input exception with 237.
The text was updated successfully, but these errors were encountered:
I imagine this is rather an edge case given that we are living in Czech republic and our language use specific letters (ÁÉĚÍÝÓÚŮŽŠČŘĎŤŇáéěíýóúůžščřďťň), but for us it was a huge blocker that required fast workaround and I would be glad if you could take a look at it.
We are using your package to extract data from .pfx/.p12 files and turns out that if friendlyName attribute contains any of the mentioned letters, the parsePkcs12 function cannot handle it and throws exception.
Following certificate (posting in bytes so you can reproduce it easily), contains letter "í" in the friendlyName:
cert_in_bytes.txt
Password to decode it is: 123456789
When you pass it to parsePkcs12 function, it is gonna fail eventually in ASN1Sequence.fromBytes() constructor call with exception:
Format exception, 237 character is invalid
...according to extended ASCII table, 237 is equivalent of "í". That is how I found the issue. Initially I thought it will be problem if it appears anywhere inside a certificate (like in SUBJECT, ORGANIZATION etc.), but no, it is only a problem if it is in the friendlyName.As this is represented under following OID
I am able to look for those bytes in the decryptes Uint8List and find and replace diacritics that follows after it.
Functions added to hex_utils.dart
Usage in parsePkcs12.dart (look for function findAndSanitizeFriendlyName)
Therefore to reproduce it on the current version of basic utils, you can open any project and just call:
You should get the Format Input exception with 237.
The text was updated successfully, but these errors were encountered: