src/PublicKeyInfo.js
import * as asn1js from "asn1js";
import { getParametersValue, clearProps } from "pvutils";
import { getCrypto } from "./common.js";
import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
import ECPublicKey from "./ECPublicKey.js";
import RSAPublicKey from "./RSAPublicKey.js";
//**************************************************************************************
/**
* Class from RFC5280
*/
export default class PublicKeyInfo
{
//**********************************************************************************
/**
* Constructor for PublicKeyInfo class
* @param {Object} [parameters={}]
* @property {Object} [schema] asn1js parsed value
*/
constructor(parameters = {})
{
//region Internal properties of the object
/**
* @type {AlgorithmIdentifier}
* @description Algorithm identifier
*/
this.algorithm = getParametersValue(parameters, "algorithm", PublicKeyInfo.defaultValues("algorithm"));
/**
* @type {BitString}
* @description Subject public key value
*/
this.subjectPublicKey = getParametersValue(parameters, "subjectPublicKey", PublicKeyInfo.defaultValues("subjectPublicKey"));
if("parsedKey" in parameters)
/**
* @type {ECPublicKey|RSAPublicKey}
* @description Parsed public key value
*/
this.parsedKey = getParametersValue(parameters, "parsedKey", PublicKeyInfo.defaultValues("parsedKey"));
//endregion
//region If input argument array contains "schema" for this object
if("schema" in parameters)
this.fromSchema(parameters.schema);
//endregion
//region If input argument array contains "json" for this object
if("json" in parameters)
this.fromJSON(parameters.json);
//endregion
}
//**********************************************************************************
/**
* Return default values for all class members
* @param {string} memberName String name for a class member
*/
static defaultValues(memberName)
{
switch(memberName)
{
case "algorithm":
return new AlgorithmIdentifier();
case "subjectPublicKey":
return new asn1js.BitString();
default:
throw new Error(`Invalid member name for PublicKeyInfo class: ${memberName}`);
}
}
//**********************************************************************************
/**
* Return value of asn1js schema for current class
* @param {Object} parameters Input parameters for the schema
* @returns {Object} asn1js schema object
*/
static schema(parameters = {})
{
//SubjectPublicKeyInfo ::= Sequence {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }
/**
* @type {Object}
* @property {string} [blockName]
* @property {string} [algorithm]
* @property {string} [subjectPublicKey]
*/
const names = getParametersValue(parameters, "names", {});
return (new asn1js.Sequence({
name: (names.blockName || ""),
value: [
AlgorithmIdentifier.schema(names.algorithm || {}),
new asn1js.BitString({ name: (names.subjectPublicKey || "") })
]
}));
}
//**********************************************************************************
/**
* Convert parsed asn1js object into current class
* @param {!Object} schema
*/
fromSchema(schema)
{
//region Clear input data first
clearProps(schema, [
"algorithm",
"subjectPublicKey"
]);
//endregion
//region Check the schema is valid
const asn1 = asn1js.compareSchema(schema,
schema,
PublicKeyInfo.schema({
names: {
algorithm: {
names: {
blockName: "algorithm"
}
},
subjectPublicKey: "subjectPublicKey"
}
})
);
if(asn1.verified === false)
throw new Error("Object's schema was not verified against input data for PublicKeyInfo");
//endregion
//region Get internal properties from parsed schema
this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm });
this.subjectPublicKey = asn1.result.subjectPublicKey;
switch(this.algorithm.algorithmId)
{
case "1.2.840.10045.2.1": // ECDSA
if("algorithmParams" in this.algorithm)
{
if(this.algorithm.algorithmParams instanceof asn1js.ObjectIdentifier)
{
try
{
this.parsedKey = new ECPublicKey({
namedCurve: this.algorithm.algorithmParams.valueBlock.toString(),
schema: this.subjectPublicKey.valueBlock.valueHex
});
}
catch(ex){} // Could be a problems during recognision of internal public key data here. Let's ignore them.
}
}
break;
case "1.2.840.113549.1.1.1": // RSA
{
const publicKeyASN1 = asn1js.fromBER(this.subjectPublicKey.valueBlock.valueHex);
if(publicKeyASN1.offset !== (-1))
{
try
{
this.parsedKey = new RSAPublicKey({ schema: publicKeyASN1.result });
}
catch(ex){} // Could be a problems during recognision of internal public key data here. Let's ignore them.
}
}
break;
default:
}
//endregion
}
//**********************************************************************************
/**
* Convert current object to asn1js object and set correct values
* @returns {Object} asn1js object
*/
toSchema()
{
//region Construct and return new ASN.1 schema for this object
return (new asn1js.Sequence({
value: [
this.algorithm.toSchema(),
this.subjectPublicKey
]
}));
//endregion
}
//**********************************************************************************
/**
* Convertion for the class to JSON object
* @returns {Object}
*/
toJSON()
{
//region Return common value in case we do not have enough info fo making JWK
if(("parsedKey" in this) === false)
{
return {
algorithm: this.algorithm.toJSON(),
subjectPublicKey: this.subjectPublicKey.toJSON()
};
}
//endregion
//region Making JWK
const jwk = {};
switch(this.algorithm.algorithmId)
{
case "1.2.840.10045.2.1": // ECDSA
jwk.kty = "EC";
break;
case "1.2.840.113549.1.1.1": // RSA
jwk.kty = "RSA";
break;
default:
}
const publicKeyJWK = this.parsedKey.toJSON();
for(const key of Object.keys(publicKeyJWK))
jwk[key] = publicKeyJWK[key];
return jwk;
//endregion
}
//**********************************************************************************
/**
* Convert JSON value into current object
* @param {Object} json
*/
fromJSON(json)
{
if("kty" in json)
{
switch(json.kty.toUpperCase())
{
case "EC":
this.parsedKey = new ECPublicKey({ json });
this.algorithm = new AlgorithmIdentifier({
algorithmId: "1.2.840.10045.2.1",
algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve })
});
break;
case "RSA":
this.parsedKey = new RSAPublicKey({ json });
this.algorithm = new AlgorithmIdentifier({
algorithmId: "1.2.840.113549.1.1.1",
algorithmParams: new asn1js.Null()
});
break;
default:
throw new Error(`Invalid value for "kty" parameter: ${json.kty}`);
}
this.subjectPublicKey = new asn1js.BitString({ valueHex: this.parsedKey.toSchema().toBER(false) });
}
}
//**********************************************************************************
importKey(publicKey)
{
//region Initial variables
let sequence = Promise.resolve();
const _this = this;
//endregion
//region Initial check
if(typeof publicKey === "undefined")
return Promise.reject("Need to provide publicKey input parameter");
//endregion
//region Get a "crypto" extension
const crypto = getCrypto();
if(typeof crypto === "undefined")
return Promise.reject("Unable to create WebCrypto object");
//endregion
//region Export public key
sequence = sequence.then(() =>
crypto.exportKey("spki", publicKey));
//endregion
//region Initialize internal variables by parsing exported value
sequence = sequence.then(
/**
* @param {ArrayBuffer} exportedKey
*/
exportedKey =>
{
const asn1 = asn1js.fromBER(exportedKey);
try
{
_this.fromSchema(asn1.result);
}
catch(exception)
{
return Promise.reject("Error during initializing object from schema");
}
return undefined;
},
error => Promise.reject(`Error during exporting public key: ${error}`)
);
//endregion
return sequence;
}
//**********************************************************************************
}
//**************************************************************************************