PeculiarVentures / x509

@peculiar/x509 is an easy to use TypeScript/Javascript library based on @peculiar/asn1-schema that makes generating X.509 Certificates and Certificate Requests as well as validating certificate chains easy
https://peculiarventures.github.io/x509/
MIT License
78 stars 10 forks source link

Can I create a set of certificates? #68

Closed kogratte closed 6 months ago

kogratte commented 6 months ago

Hey :)

I'm trying to create a set of certificates, which can be drawn as below:

I'm struggling with that...

For RootCert, I'm building it like that:

const alg = {
    name: 'RSASSA-PKCS1-v1_5',
    hash: 'SHA-256',
    publicExponent: new Uint8Array([1, 0, 1]),
    modulusLength: 4096,
};

const keys = await crypto.subtle.generateKey(alg, true, ['sign', 'verify']);

const cert = await x509.X509CertificateGenerator.createSelfSigned({
        serialNumber: '01',
        name: 'CN=Test',
        notBefore: new Date('2020/01/01'),
        notAfter: new Date('2020/01/02'),
        signingAlgorithm: alg,
        keys,
        extensions: [
            new x509.BasicConstraintsExtension(true, 2, true),
            new x509.ExtendedKeyUsageExtension(
                ['1.2.3.4.5.6.7', '2.3.4.5.6.7.8'],
                true
            ),
            new x509.KeyUsagesExtension(
                x509.KeyUsageFlags.digitalSignature |
                    x509.KeyUsageFlags.keyEncipherment |
                    x509.KeyUsageFlags.cRLSign |
                    x509.KeyUsageFlags.keyCertSign,
                true
            ),
            await x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
        ],
    });

When it comes to create a child cert, I'm trying to do the following:

const keys2 = await crypto.subtle.generateKey(alg, false, ['decrypt']);

const c2 = await x509.X509CertificateGenerator.create({
        publicKey: keys2.publicKey,
        serialNumber: '01',
        signingKey: cert.privateKey!,
        notBefore: new Date('2020/01/01'),
        notAfter: new Date('2020/01/02'),
        signingAlgorithm: alg,
        extensions: [
            await x509.SubjectKeyIdentifierExtension.create(keys2.publicKey),
        ],
    });

But.. I couldn't say I've been successful!

Is it possible to achieve what I'm trying to do? If so, what am I missing?

microshine commented 6 months ago

In the example you provided, a root certificate is created with the name CN=Test, but no issuer name is specified when issuing a user certificate. As per specification requirements, an issued certificate needs to have an issuer's name. The X509ChainBuilder performs this verification, and if the names do not match, it will exclude the certificate from the chain construction.

For a practical example of certificate chain creation, you can refer to the script in this test: https://github.com/PeculiarVentures/x509/blob/master/test/issues.ts#L10-L60.

kogratte commented 6 months ago

I'll take a look :) Thanks :) (please, do not close this issue for the time being, I would like to share the result once working as expected)

kogratte commented 6 months ago

Q: What is the point of the intermediate cert in the example you point me out? I did a quick research though github using your lib, and found a lot of different details.

kogratte commented 6 months ago

A reproduction repo is available here: https://github.com/kogratte/x509-sandbox

As you may see here, tests are failing. I may have miss-understood something, but I'm not able to find it :/

What is different between my implementation and the one from the example?

microshine commented 6 months ago

I've updated your example. It works.

const alg = {
  name: "RSASSA-PKCS1-v1_5",
  hash: "SHA-256",
  publicExponent: new Uint8Array([1, 0, 1]),
  modulusLength: 4096,
};
const rootKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const rootCert = await x509.X509CertificateGenerator.createSelfSigned({
  serialNumber: "01",
  name: "CN=Root",
  notBefore: new Date("2023-12-19"),
  notAfter: new Date("2033-12-19"),
  keys: rootKeys,
  signingAlgorithm: alg,
  extensions: [
    new x509.BasicConstraintsExtension(true, 2, true),
    new x509.ExtendedKeyUsageExtension(
      ["1.2.3.4.5.6.7", "2.3.4.5.6.7.8"],
      true
    ),
    new x509.KeyUsagesExtension(
      x509.KeyUsageFlags.cRLSign |
      x509.KeyUsageFlags.keyCertSign,
      true
    ),
    await x509.SubjectKeyIdentifierExtension.create(rootKeys.publicKey, false, crypto),
  ],
}, crypto);

const leafKeys = await crypto.subtle.generateKey({
  ...alg,
  name: "RSA-OAEP", // We should use RSA-OAEP for encryption
}, true, ["encrypt", "decrypt"]);
const leafCert = await x509.X509CertificateGenerator.create({
  serialNumber: "03",
  subject: "CN=Leaf",
  issuer: rootCert.subject,
  notBefore: new Date("2023-12-19"),
  notAfter: new Date("2024-12-19"),
  signingKey: rootKeys.privateKey,
  publicKey: leafKeys.publicKey,
  signingAlgorithm: alg,
  extensions: [
    new x509.KeyUsagesExtension(
      x509.KeyUsageFlags.dataEncipherment,
      true,
    ),
    new x509.BasicConstraintsExtension(false),
    await x509.AuthorityKeyIdentifierExtension.create(rootCert, false, crypto),
    await x509.SubjectKeyIdentifierExtension.create(leafKeys.publicKey, false, crypto),
  ],
}, crypto);

// console.log([
//   rootCert.toString("pem"),
//   leafCert.toString("pem"),
// ].join("\n"));

const chain = new x509.X509ChainBuilder({
  certificates: [rootCert],
});
const items = await chain.build(leafCert, crypto);
assert.strictEqual(items.length, 2);
kogratte commented 6 months ago

It works!

I pushed the working codebase. I can keep it online or you can grab it if you want, but it will anyway be available for futur users! Thanks for assistance!