@libp2p/crypto
Version:
Crypto primitives for libp2p
213 lines (178 loc) • 6.47 kB
text/typescript
import { InvalidParametersError } from '@libp2p/interface'
import { Uint8ArrayList } from 'uint8arraylist'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { decodeDer, encodeBitString, encodeInteger, encodeOctetString, encodeSequence } from '../rsa/der.js'
import { ECDSAPrivateKey as ECDSAPrivateKeyClass, ECDSAPublicKey as ECDSAPublicKeyClass } from './ecdsa.js'
import { generateECDSAKey } from './index.js'
import type { Curve } from '../ecdh/index.js'
import type { ECDSAPublicKey, ECDSAPrivateKey } from '@libp2p/interface'
// 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve)
const OID_256 = Uint8Array.from([0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07])
// 1.3.132.0.34 secp384r1 (SECG (Certicom) named elliptic curve)
const OID_384 = Uint8Array.from([0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22])
// 1.3.132.0.35 secp521r1 (SECG (Certicom) named elliptic curve)
const OID_521 = Uint8Array.from([0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23])
const P_256_KEY_JWK = {
ext: true,
kty: 'EC',
crv: 'P-256'
}
const P_384_KEY_JWK = {
ext: true,
kty: 'EC',
crv: 'P-384'
}
const P_521_KEY_JWK = {
ext: true,
kty: 'EC',
crv: 'P-521'
}
const P_256_KEY_LENGTH = 32
const P_384_KEY_LENGTH = 48
const P_521_KEY_LENGTH = 66
export function unmarshalECDSAPrivateKey (bytes: Uint8Array): ECDSAPrivateKey {
const message = decodeDer(bytes)
return pkiMessageToECDSAPrivateKey(message)
}
export function pkiMessageToECDSAPrivateKey (message: any): ECDSAPrivateKey {
const privateKey = message[1]
const d = uint8ArrayToString(privateKey, 'base64url')
const coordinates: Uint8Array = message[2][1][0]
const offset = 1
let x: string
let y: string
if (privateKey.byteLength === P_256_KEY_LENGTH) {
x = uint8ArrayToString(coordinates.subarray(offset, offset + P_256_KEY_LENGTH), 'base64url')
y = uint8ArrayToString(coordinates.subarray(offset + P_256_KEY_LENGTH), 'base64url')
return new ECDSAPrivateKeyClass({
...P_256_KEY_JWK,
key_ops: ['sign'],
d,
x,
y
})
}
if (privateKey.byteLength === P_384_KEY_LENGTH) {
x = uint8ArrayToString(coordinates.subarray(offset, offset + P_384_KEY_LENGTH), 'base64url')
y = uint8ArrayToString(coordinates.subarray(offset + P_384_KEY_LENGTH), 'base64url')
return new ECDSAPrivateKeyClass({
...P_384_KEY_JWK,
key_ops: ['sign'],
d,
x,
y
})
}
if (privateKey.byteLength === P_521_KEY_LENGTH) {
x = uint8ArrayToString(coordinates.subarray(offset, offset + P_521_KEY_LENGTH), 'base64url')
y = uint8ArrayToString(coordinates.subarray(offset + P_521_KEY_LENGTH), 'base64url')
return new ECDSAPrivateKeyClass({
...P_521_KEY_JWK,
key_ops: ['sign'],
d,
x,
y
})
}
throw new InvalidParametersError(`Private key length was wrong length, got ${privateKey.byteLength}, expected 32, 48 or 66`)
}
export function unmarshalECDSAPublicKey (bytes: Uint8Array): ECDSAPublicKey {
const message = decodeDer(bytes)
return pkiMessageToECDSAPublicKey(message)
}
export function pkiMessageToECDSAPublicKey (message: any): ECDSAPublicKey {
const coordinates = message[1][1][0]
const offset = 1
let x: string
let y: string
if (coordinates.byteLength === ((P_256_KEY_LENGTH * 2) + 1)) {
x = uint8ArrayToString(coordinates.subarray(offset, offset + P_256_KEY_LENGTH), 'base64url')
y = uint8ArrayToString(coordinates.subarray(offset + P_256_KEY_LENGTH), 'base64url')
return new ECDSAPublicKeyClass({
...P_256_KEY_JWK,
key_ops: ['verify'],
x,
y
})
}
if (coordinates.byteLength === ((P_384_KEY_LENGTH * 2) + 1)) {
x = uint8ArrayToString(coordinates.subarray(offset, offset + P_384_KEY_LENGTH), 'base64url')
y = uint8ArrayToString(coordinates.subarray(offset + P_384_KEY_LENGTH), 'base64url')
return new ECDSAPublicKeyClass({
...P_384_KEY_JWK,
key_ops: ['verify'],
x,
y
})
}
if (coordinates.byteLength === ((P_521_KEY_LENGTH * 2) + 1)) {
x = uint8ArrayToString(coordinates.subarray(offset, offset + P_521_KEY_LENGTH), 'base64url')
y = uint8ArrayToString(coordinates.subarray(offset + P_521_KEY_LENGTH), 'base64url')
return new ECDSAPublicKeyClass({
...P_521_KEY_JWK,
key_ops: ['verify'],
x,
y
})
}
throw new InvalidParametersError(`coordinates were wrong length, got ${coordinates.byteLength}, expected 65, 97 or 133`)
}
export function privateKeyToPKIMessage (privateKey: JsonWebKey): Uint8Array {
return encodeSequence([
encodeInteger(Uint8Array.from([1])), // header
encodeOctetString(uint8ArrayFromString(privateKey.d ?? '', 'base64url')), // body
encodeSequence([ // PKIProtection
getOID(privateKey.crv)
], 0xA0),
encodeSequence([ // extraCerts
encodeBitString(
new Uint8ArrayList(
Uint8Array.from([0x04]),
uint8ArrayFromString(privateKey.x ?? '', 'base64url'),
uint8ArrayFromString(privateKey.y ?? '', 'base64url')
)
)
], 0xA1)
]).subarray()
}
export function publicKeyToPKIMessage (publicKey: JsonWebKey): Uint8Array {
return encodeSequence([
encodeInteger(Uint8Array.from([1])), // header
encodeSequence([ // PKIProtection
getOID(publicKey.crv)
], 0xA0),
encodeSequence([ // extraCerts
encodeBitString(
new Uint8ArrayList(
Uint8Array.from([0x04]),
uint8ArrayFromString(publicKey.x ?? '', 'base64url'),
uint8ArrayFromString(publicKey.y ?? '', 'base64url')
)
)
], 0xA1)
]).subarray()
}
function getOID (curve?: string): Uint8Array {
if (curve === 'P-256') {
return OID_256
}
if (curve === 'P-384') {
return OID_384
}
if (curve === 'P-521') {
return OID_521
}
throw new InvalidParametersError(`Invalid curve ${curve}`)
}
export async function generateECDSAKeyPair (curve: Curve = 'P-256'): Promise<ECDSAPrivateKey> {
const key = await generateECDSAKey(curve)
return new ECDSAPrivateKeyClass(key.privateKey)
}
export function ensureECDSAKey (key: Uint8Array, length: number): Uint8Array {
key = Uint8Array.from(key ?? [])
if (key.length !== length) {
throw new InvalidParametersError(`Key must be a Uint8Array of length ${length}, got ${key.length}`)
}
return key
}