@libp2p/crypto
Version:
Crypto primitives for libp2p
172 lines • 6.11 kB
JavaScript
import { InvalidParametersError, InvalidPublicKeyError } from '@libp2p/interface';
import { sha256 } from '@noble/hashes/sha256';
import { create } from 'multiformats/hashes/digest';
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
import * as pb from '../keys.js';
import { decodeDer, encodeBitString, encodeInteger, encodeSequence } from './der.js';
import { RSAPrivateKey as RSAPrivateKeyClass, RSAPublicKey as RSAPublicKeyClass } from './rsa.js';
import { generateRSAKey, rsaKeySize } from './index.js';
export const MAX_RSA_KEY_SIZE = 8192;
const SHA2_256_CODE = 0x12;
const MAX_RSA_JWK_SIZE = 1062;
const RSA_ALGORITHM_IDENTIFIER = Uint8Array.from([
0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00
]);
/**
* Convert a PKCS#1 in ASN1 DER format to a JWK private key
*/
export function pkcs1ToJwk(bytes) {
const message = decodeDer(bytes);
return pkcs1MessageToJwk(message);
}
/**
* Convert a PKCS#1 in ASN1 DER format to a JWK private key
*/
export function pkcs1MessageToJwk(message) {
return {
n: uint8ArrayToString(message[1], 'base64url'),
e: uint8ArrayToString(message[2], 'base64url'),
d: uint8ArrayToString(message[3], 'base64url'),
p: uint8ArrayToString(message[4], 'base64url'),
q: uint8ArrayToString(message[5], 'base64url'),
dp: uint8ArrayToString(message[6], 'base64url'),
dq: uint8ArrayToString(message[7], 'base64url'),
qi: uint8ArrayToString(message[8], 'base64url'),
kty: 'RSA'
};
}
/**
* Convert a JWK private key into PKCS#1 in ASN1 DER format
*/
export function jwkToPkcs1(jwk) {
if (jwk.n == null || jwk.e == null || jwk.d == null || jwk.p == null || jwk.q == null || jwk.dp == null || jwk.dq == null || jwk.qi == null) {
throw new InvalidParametersError('JWK was missing components');
}
return encodeSequence([
encodeInteger(Uint8Array.from([0])),
encodeInteger(uint8ArrayFromString(jwk.n, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.e, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.d, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.p, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.q, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.dp, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.dq, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.qi, 'base64url'))
]).subarray();
}
/**
* Convert a PKIX in ASN1 DER format to a JWK public key
*/
export function pkixToJwk(bytes) {
const message = decodeDer(bytes, {
offset: 0
});
return pkixMessageToJwk(message);
}
export function pkixMessageToJwk(message) {
const keys = decodeDer(message[1], {
offset: 0
});
// this looks fragile but DER is a canonical format so we are safe to have
// deeply property chains like this
return {
kty: 'RSA',
n: uint8ArrayToString(keys[0], 'base64url'),
e: uint8ArrayToString(keys[1], 'base64url')
};
}
/**
* Convert a JWK public key to PKIX in ASN1 DER format
*/
export function jwkToPkix(jwk) {
if (jwk.n == null || jwk.e == null) {
throw new InvalidParametersError('JWK was missing components');
}
const subjectPublicKeyInfo = encodeSequence([
RSA_ALGORITHM_IDENTIFIER,
encodeBitString(encodeSequence([
encodeInteger(uint8ArrayFromString(jwk.n, 'base64url')),
encodeInteger(uint8ArrayFromString(jwk.e, 'base64url'))
]))
]);
return subjectPublicKeyInfo.subarray();
}
/**
* Turn PKCS#1 DER bytes into a PrivateKey
*/
export function pkcs1ToRSAPrivateKey(bytes) {
const message = decodeDer(bytes);
return pkcs1MessageToRSAPrivateKey(message);
}
/**
* Turn PKCS#1 DER bytes into a PrivateKey
*/
export function pkcs1MessageToRSAPrivateKey(message) {
const jwk = pkcs1MessageToJwk(message);
return jwkToRSAPrivateKey(jwk);
}
/**
* Turn a PKIX message into a PublicKey
*/
export function pkixToRSAPublicKey(bytes, digest) {
if (bytes.byteLength >= MAX_RSA_JWK_SIZE) {
throw new InvalidPublicKeyError('Key size is too large');
}
const message = decodeDer(bytes, {
offset: 0
});
return pkixMessageToRSAPublicKey(message, bytes, digest);
}
export function pkixMessageToRSAPublicKey(message, bytes, digest) {
const jwk = pkixMessageToJwk(message);
if (digest == null) {
const hash = sha256(pb.PublicKey.encode({
Type: pb.KeyType.RSA,
Data: bytes
}));
digest = create(SHA2_256_CODE, hash);
}
return new RSAPublicKeyClass(jwk, digest);
}
export function jwkToRSAPrivateKey(jwk) {
if (rsaKeySize(jwk) > MAX_RSA_KEY_SIZE) {
throw new InvalidParametersError('Key size is too large');
}
const keys = jwkToJWKKeyPair(jwk);
const hash = sha256(pb.PublicKey.encode({
Type: pb.KeyType.RSA,
Data: jwkToPkix(keys.publicKey)
}));
const digest = create(SHA2_256_CODE, hash);
return new RSAPrivateKeyClass(keys.privateKey, new RSAPublicKeyClass(keys.publicKey, digest));
}
export async function generateRSAKeyPair(bits) {
if (bits > MAX_RSA_KEY_SIZE) {
throw new InvalidParametersError('Key size is too large');
}
const keys = await generateRSAKey(bits);
const hash = sha256(pb.PublicKey.encode({
Type: pb.KeyType.RSA,
Data: jwkToPkix(keys.publicKey)
}));
const digest = create(SHA2_256_CODE, hash);
return new RSAPrivateKeyClass(keys.privateKey, new RSAPublicKeyClass(keys.publicKey, digest));
}
/**
* Takes a jwk key and returns a JWK KeyPair
*/
export function jwkToJWKKeyPair(key) {
if (key == null) {
throw new InvalidParametersError('Missing key parameter');
}
return {
privateKey: key,
publicKey: {
kty: key.kty,
n: key.n,
e: key.e
}
};
}
//# sourceMappingURL=utils.js.map