PKCS #11 URIs: naming a token and an object on it (RFC 7512)
parseUri reads one into a record and formatUri writes the record back.
The path component carries what identifies the object: token, manufacturer, serial, model, the three library-* attributes, the three slot-* attributes, object, type and id. The query component carries what tells a consumer how to reach it: pin-source, pin-value, module-name and module-path. A vendor attribute in either component is kept under its own name rather than dropped. A vendor query attribute is carried as a list of its values, which sec. 2.4 permits to repeat and leaves to the consumer to interpret; a vendor path attribute carries one value.
Parsing is fail-closed and follows the ABNF of sec. 2.3 and sec. 2.4 rather than a general URI reader: a character a component does not admit unencoded is refused, a truncated or non-hex escape is refused, an empty attribute between two delimiters is refused, a repeated attribute is refused wherever the RFC does not permit one, and the attributes the RFC writes as digits or as literal alternatives are read that way rather than through an escape. id is decoded to a Buffer, because it names bytes and not text. A URI carrying both pin-source and pin-value is refused, and so is a module-path that is not absolute, both of which sec. 2.4 asks a consumer to treat as invalid.
An attribute name and a type value are read whichever case they arrive in, RFC 5234 sec. 2.3 making an ABNF quoted literal case-insensitive, so PIN-SOURCE is the pin-source attribute and is held to the rules pin-source is held to rather than passing as vendor data. Writing produces the canonical form, which is what lets two URIs naming one object be compared as strings, and every rule parsing refuses writing refuses too, so the two directions cannot drift. This is a syntax, not a driver: nothing here loads a PKCS #11 module or talks to a token.
pki.pkcs11.parseUri
pki.pkcs11.parseUri(uri) -> parsed
Read an RFC 7512 PKCS #11 URI. Returns { path, vendorPath, query, vendorQuery }, each a prototype-less record, so a computed read answers from the URI and never from Object.prototype. path.id is a Buffer, since the attribute names bytes; path.slotId and path.libraryVersion ({ major, minor }) are numbers. A vendorQuery attribute carries a list of its values, which sec. 2.4 permits to repeat; a vendorPath attribute carries one value.
Fail-closed against the ABNF rather than a general URI reader: a character the component admits only percent-encoded, a truncated or non-hex escape, a value that is not valid UTF-8, an empty attribute between two delimiters, a repeated attribute the RFC does not permit to repeat, a percent-escape inside type, slot-id or library-version, a type outside the five the RFC enumerates, a library-version component above 255, a URI carrying both pin-source and pin-value, and a module-path that is not absolute are each refused with their own pkcs11/* code. An attribute name is matched case-insensitively, so a standard attribute cannot arrive spelled differently and pass as vendor data.
Example
var u = pki.pkcs11.parseUri("pkcs11:token=My%20Token;object=signing-key;type=private?module-name=softhsm2");
u.path.token; // "My Token"
u.path.type; // "private"
u.query.moduleName; // "softhsm2"
References
- spec RFC 7512
pki.pkcs11.formatUri
pki.pkcs11.formatUri(record) -> string
Write an RFC 7512 PKCS #11 URI from { path, vendorPath, query, vendorQuery }, the shape parseUri returns. What comes out is the canonical form rather than the input byte for byte: attributes are emitted in the order the RFC lists them, each value is percent-encoded to what its own component admits unencoded, id is encoded whole with uppercase hexadecimal, slot-id drops leading zeros and library-version states its minor. That is what makes two URIs naming one object compare equal as strings, which sec. 2.6 asks of a consumer, and the form is stable: writing it again changes nothing.
The rules parseUri refuses are refused here too rather than written out: a type outside the five the RFC enumerates, both pin-source and pin-value, a relative module-path, and a number this toolkit would not read back. An attribute the RFC does not define is named in the refusal, so a misspelled field is a refusal rather than a silently dropped one; put a vendor attribute under vendorPath or vendorQuery, where its name is held to the vendor grammar and refused if a standard attribute owns it, so a name carrying a delimiter cannot put an attribute into the URI the caller never named. A vendorQuery attribute is given as a list and its values are written in the order the list gives them, which is the shape parseUri returns; a vendorPath attribute is given one value. A vendor name given no value, or given an empty list, is refused as well, since the name is the attribute the caller asked for.
Example
pki.pkcs11.formatUri({ path: { token: "My Token", type: "private" }, query: { moduleName: "softhsm2" } });
// -> "pkcs11:token=My%20Token;type=private?module-name=softhsm2"
References
- spec RFC 7512