loadPrivateKeyFromPKCS8Ed25519 static method

Uint8List loadPrivateKeyFromPKCS8Ed25519(
  1. String pem
)

Reads a not encrypted PKCS#8 file and returns a 32-Byte-Ed25519-Seed.

PKCS#8 structure (RFC8410) with OID 1.3.101.112 (Ed25519):

PrivateKeyInfo ::= SEQUENCE { version INTEGER (0), privateKeyAlgorithm SEQUENCE { algorithm OBJECT IDENTIFIER (1.3.101.112), parameters (ABSENT oder NULL) }, privateKey OCTET STRING -- the seed }

Implementation

static Uint8List loadPrivateKeyFromPKCS8Ed25519(String pem) {
  const header = '-----BEGIN PRIVATE KEY-----';
  const footer = '-----END PRIVATE KEY-----';

  if (!pem.contains(header) || !pem.contains(footer)) {
    throw ArgumentError(
      'Not a valid PKCS#8 PEM (BEGIN/END PRIVATE KEY missing).',
    );
  }

  final body = pem
      .replaceAll(header, '')
      .replaceAll(footer, '')
      .replaceAll(RegExp(r'\s'), '');

  final derBytes = base64.decode(body);

  final asn1Parser = ASN1Parser(derBytes);
  final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
  if (topLevelSeq.elements == null || topLevelSeq.elements!.length < 3) {
    throw StateError('Unexpected structure in PKCS#8, to few fields');
  }

  final version = topLevelSeq.elements![0] as ASN1Integer;
  if (version.integer?.toInt() != 0) {
    throw StateError('Unexpected structure in PKCS#8, to few fields');
  }

  final algorithmSeq = topLevelSeq.elements![1] as ASN1Sequence;
  if (algorithmSeq.elements == null || algorithmSeq.elements!.isEmpty) {
    throw StateError('No algorithm identifier found');
  }
  final objectIdentifier = (algorithmSeq.elements![0] as ASN1ObjectIdentifier)
      .objectIdentifierAsString;
  if ("1.3.101.112" != objectIdentifier) {
    throw ArgumentError(
      'The key is not a Ed25519-Key (OID=$objectIdentifier)!',
    );
  }

  final privateKeyOctetStr = topLevelSeq.elements![2] as ASN1OctetString;
  final privateKeyBytes = privateKeyOctetStr.valueBytes;

  Uint8List? ed25519Seed;
  final innerParser = ASN1Parser(privateKeyBytes);
  final possibleInner = innerParser.nextObject();

  if (possibleInner is ASN1OctetString) {
    final inner = possibleInner.valueBytes;
    if (inner?.length == 32) {
      ed25519Seed = inner;
    }
  }

  if (ed25519Seed == null) {
    if (privateKeyBytes?.length == 32) {
      ed25519Seed = privateKeyBytes;
    } else if (privateKeyBytes?.length == 64) {
      ed25519Seed = privateKeyBytes?.sublist(0, 32);
    } else {
      throw StateError(
        'PrivateKey has no valid length (32 or 64); found length: ${privateKeyBytes?.length}',
      );
    }
  }

  return ed25519Seed ?? Uint8List(0);
}