webcrypto-liner
Advanced tools
Comparing version 0.1.8 to 0.1.9
{ | ||
"name": "webcrypto-liner", | ||
"version": "0.1.8", | ||
"version": "0.1.9", | ||
"description": "A WebCrypto pollyfill that \"smooths out\" the rough-edges in existing User Agent implementations.", | ||
@@ -8,4 +8,4 @@ "main": "build/index.js", | ||
"build": "npm run build:es5", | ||
"build:es5": "tsc --module commonjs --target es5", | ||
"build:es2015": "tsc --module es2015 --target es2015", | ||
"build:es5": "tsc", | ||
"build:es2015": "tsc -p tsconfig.es2015.json", | ||
"build:rollup": "npm run build:es2015 && rollup -o=index.js -i=build/index.js", | ||
@@ -12,0 +12,0 @@ "build:webpack": "webpack", |
@@ -40,4 +40,4 @@ # webcrypto-liner | ||
| Encryption/Decryption | RSA-OAEP, AES-CBC, and AES-GCM | | ||
| Sign/Verify | RSA-PSS, ~~RSASSA-PKCS1-v1_5,~~ and ECDSA | | ||
| Hash | SHA-1, SHA-224, SHA-256, and SHA-384 | | ||
| Sign/Verify | RSA-PSS, RSASSA_PKCS1-v1_5 and ECDSA | | ||
| Hash | SHA-1, and SHA-224 | | ||
| Derive Key/Bits | ECDH | | ||
@@ -114,2 +114,3 @@ | Keywrap | AES-GCM, AES-CBC | | ||
- **Do I need to include elliptic.js?** No, not unless you want to use the algorithms it exposes. | ||
- **How are random numbers generated?** We use two libraries for crypto operations in Javascript, [asymcrypto](https://github.com/vibornoff/asmcrypto.js/blob/bffc0674c7756dff16c69c5665b9eea2e0409736/src/random/globals.js#L4) and [ellipticjs](https://github.com/indutny/elliptic/blob/cbace4683a4a548dc0306ef36756151a20299cd5/dist/elliptic.js#L7464) both rely on [window.crypto.getRandomValues](http://caniuse.com/#feat=getrandomvalues) where available. `asymcrypto` also has a fallback mechanism where it generates its own random numbers if not present. | ||
- **How big is the total package?** Right now, if you include all optional dependencies (minfied) the package is ~300 KB, if you include only ECC or only RSA support that is lowered to about 180 KB. Additionally you will see GZIP compression provide about 30% savings above and beyond that. If you use `webcrypto-liner` as just an interopability shim and do not use any of the optional third-party libraries it will be under 44 KB in size. | ||
@@ -116,0 +117,0 @@ - **Will it work in Node?** No. It is compiles to pure Javascript but uses the `window` object so it wont work in Node at this time. With some minor changes it should also be able to work in Node also but you really should be using [node-webcrypto-ossl](https://github.com/PeculiarVentures/node-webcrypto-ossl) on Node instead. |
import { BaseCrypto, AlgorithmNames, AlgorithmError, Base64Url } from "webcrypto-core"; | ||
import { LinerError } from "../crypto"; | ||
import { LinerError } from "../error"; | ||
import { CryptoKey, CryptoKeyPair } from "../key"; | ||
import { string2buffer, buffer2string, concat } from "../helper"; | ||
import * as asmCrypto from "asmcrypto.js"; | ||
import { nativeCrypto } from "../init"; | ||
@@ -19,130 +18,149 @@ | ||
static generateKey(alg: AesKeyGenParams, extractable: boolean, keyUsage: string[]): PromiseLike<CryptoKey> { | ||
return new Promise<CryptoKey>(resolve => { | ||
this.checkModule(); | ||
return Promise.resolve() | ||
.then(() => { | ||
this.checkModule(); | ||
// gat random bytes for key | ||
const key = nativeCrypto.getRandomValues(new Uint8Array(alg.length / 8)); | ||
// gat random bytes for key | ||
const key = nativeCrypto.getRandomValues(new Uint8Array(alg.length / 8)); | ||
// set key params | ||
const aesKey: AesCryptoKey = new CryptoKey(); | ||
aesKey.key = key as Uint8Array; | ||
aesKey.algorithm = alg; | ||
aesKey.extractable = extractable; | ||
aesKey.type = "secret"; | ||
aesKey.usages = keyUsage; | ||
resolve(aesKey); | ||
}); | ||
// set key params | ||
const aesKey: AesCryptoKey = new CryptoKey(); | ||
aesKey.key = key as Uint8Array; | ||
aesKey.algorithm = alg; | ||
aesKey.extractable = extractable; | ||
aesKey.type = "secret"; | ||
aesKey.usages = keyUsage; | ||
return aesKey; | ||
}); | ||
} | ||
static encrypt(algorithm: Algorithm, key: AesCryptoKey, data: Uint8Array): PromiseLike<ArrayBuffer> { | ||
return new Promise(resolve => { | ||
let res: Uint8Array; | ||
switch (algorithm.name.toUpperCase()) { | ||
case AlgorithmNames.AesCBC: | ||
let algCBC = algorithm as AesCbcParams; | ||
res = asmCrypto.AES_CBC.encrypt(data, key.key, undefined, algCBC.iv) as Uint8Array; | ||
break; | ||
case AlgorithmNames.AesGCM: | ||
let algGCM = algorithm as AesGcmParams; | ||
algGCM.tagLength = algGCM.tagLength || 128; | ||
res = asmCrypto.AES_GCM.encrypt(data, key.key, algGCM.iv, algGCM.additionalData, algGCM.tagLength / 8) as Uint8Array; | ||
break; | ||
default: | ||
throw new LinerError(AlgorithmError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
resolve(res.buffer); | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
let res: Uint8Array; | ||
switch (algorithm.name.toUpperCase()) { | ||
case AlgorithmNames.AesCBC: | ||
let algCBC = algorithm as AesCbcParams; | ||
res = asmCrypto.AES_CBC.encrypt(data, key.key, undefined, algCBC.iv) as Uint8Array; | ||
break; | ||
case AlgorithmNames.AesGCM: | ||
let algGCM = algorithm as AesGcmParams; | ||
algGCM.tagLength = algGCM.tagLength || 128; | ||
res = asmCrypto.AES_GCM.encrypt(data, key.key, algGCM.iv, algGCM.additionalData, algGCM.tagLength / 8) as Uint8Array; | ||
break; | ||
default: | ||
throw new LinerError(AlgorithmError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
return res.buffer; | ||
}); | ||
} | ||
static decrypt(algorithm: Algorithm, key: AesCryptoKey, data: Uint8Array): PromiseLike<ArrayBuffer> { | ||
return new Promise(resolve => { | ||
let res: Uint8Array; | ||
return Promise.resolve() | ||
.then(() => { | ||
let res: Uint8Array; | ||
switch (algorithm.name.toUpperCase()) { | ||
case AlgorithmNames.AesCBC: | ||
let algCBC = algorithm as AesCbcParams; | ||
res = asmCrypto.AES_CBC.decrypt(data, key.key, undefined, algCBC.iv) as Uint8Array; | ||
break; | ||
case AlgorithmNames.AesGCM: | ||
let algGCM = algorithm as AesGcmParams; | ||
algGCM.tagLength = algGCM.tagLength || 128; | ||
res = asmCrypto.AES_GCM.decrypt(data, key.key, algGCM.iv, algGCM.additionalData, algGCM.tagLength / 8) as Uint8Array; | ||
break; | ||
default: | ||
throw new LinerError(AlgorithmError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
resolve(res.buffer); | ||
}); | ||
switch (algorithm.name.toUpperCase()) { | ||
case AlgorithmNames.AesCBC: | ||
let algCBC = algorithm as AesCbcParams; | ||
res = asmCrypto.AES_CBC.decrypt(data, key.key, undefined, algCBC.iv) as Uint8Array; | ||
break; | ||
case AlgorithmNames.AesGCM: | ||
let algGCM = algorithm as AesGcmParams; | ||
algGCM.tagLength = algGCM.tagLength || 128; | ||
res = asmCrypto.AES_GCM.decrypt(data, key.key, algGCM.iv, algGCM.additionalData, algGCM.tagLength / 8) as Uint8Array; | ||
break; | ||
default: | ||
throw new LinerError(AlgorithmError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
return res.buffer; | ||
}); | ||
} | ||
static wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: Algorithm): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
self.crypto.subtle.exportKey(format, key) | ||
.then((data: any) => { | ||
let raw: Uint8Array; | ||
if (!(data instanceof ArrayBuffer)) { | ||
// JWK | ||
raw = string2buffer(JSON.stringify(data)); | ||
} | ||
else { | ||
// ArrayBuffer | ||
raw = new Uint8Array(data); | ||
} | ||
return self.crypto.subtle.encrypt(wrapAlgorithm, wrappingKey, raw); | ||
}) | ||
.then(resolve, reject); | ||
}); | ||
let crypto: Crypto; | ||
return Promise.resolve() | ||
.then(() => { | ||
crypto = new Crypto(); | ||
return crypto.subtle.exportKey(format, key); | ||
}) | ||
.then((data: any) => { | ||
let raw: Uint8Array; | ||
if (!(data instanceof ArrayBuffer)) { | ||
// JWK | ||
raw = string2buffer(JSON.stringify(data)); | ||
} | ||
else { | ||
// ArrayBuffer | ||
raw = new Uint8Array(data); | ||
} | ||
return crypto.subtle.encrypt(wrapAlgorithm, wrappingKey, raw); | ||
}); | ||
} | ||
static unwrapKey(format: string, wrappedKey: Uint8Array, unwrappingKey: CryptoKey, unwrapAlgorithm: Algorithm, unwrappedKeyAlgorithm: Algorithm, extractable: boolean, keyUsages: string[]): PromiseLike<CryptoKey> { | ||
return new Promise((resolve, reject) => { | ||
self.crypto.subtle.decrypt(unwrapAlgorithm, unwrappingKey, wrappedKey) | ||
.then((data: any) => { | ||
let _data: any; | ||
if (format.toLowerCase() === "jwk") | ||
_data = JSON.parse(buffer2string(new Uint8Array(data))); | ||
else | ||
_data = new Uint8Array(data); | ||
return self.crypto.subtle.importKey(format, _data, unwrappedKeyAlgorithm, extractable, keyUsages); | ||
}) | ||
.then(resolve, reject); | ||
}); | ||
let crypto: Crypto; | ||
return Promise.resolve() | ||
.then(() => { | ||
crypto = new Crypto(); | ||
return crypto.subtle.decrypt(unwrapAlgorithm, unwrappingKey, wrappedKey); | ||
}) | ||
.then((data: any) => { | ||
let _data: any; | ||
if (format.toLowerCase() === "jwk") | ||
_data = JSON.parse(buffer2string(new Uint8Array(data))); | ||
else | ||
_data = new Uint8Array(data); | ||
return crypto.subtle.importKey(format, _data, unwrappedKeyAlgorithm, extractable, keyUsages); | ||
}); | ||
} | ||
static alg2jwk(alg: Algorithm): string { | ||
return `A${(alg as AesKeyAlgorithm).length}${/-(\w+)/i.exec(alg.name!.toUpperCase()) ![1]}`; | ||
} | ||
static jwk2alg(alg: string): Algorithm { | ||
throw new Error("Not implemented"); | ||
} | ||
static exportKey(format: string, key: AesCryptoKey): PromiseLike<JsonWebKey | ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
const raw = key.key; | ||
if (format.toLowerCase() === "jwk") { | ||
let jwk: JsonWebKey = { | ||
alg: `A${(key.algorithm as AesKeyAlgorithm).length}${/-(\w+)/i.exec(key.algorithm.name!.toUpperCase()) ![1]}`, | ||
ext: key.extractable, | ||
k: Base64Url.encode(raw), | ||
key_ops: key.usages, | ||
kty: "oct" | ||
}; | ||
resolve(jwk); | ||
} | ||
else { | ||
resolve(raw.buffer); | ||
} | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
const raw = key.key; | ||
if (format.toLowerCase() === "jwk") { | ||
let jwk: JsonWebKey = { | ||
alg: this.alg2jwk(key.algorithm as Algorithm), | ||
ext: key.extractable, | ||
k: Base64Url.encode(raw), | ||
key_ops: key.usages, | ||
kty: "oct" | ||
}; | ||
return jwk; | ||
} | ||
else { | ||
return raw.buffer; | ||
} | ||
}); | ||
} | ||
static importKey(format: string, keyData: JsonWebKey | Uint8Array, algorithm: Algorithm, extractable: boolean, keyUsages: string[]): PromiseLike<CryptoKey> { | ||
return new Promise((resolve, reject) => { | ||
let raw: Uint8Array; | ||
if (format.toLowerCase() === "jwk") { | ||
const jwk = keyData as JsonWebKey; | ||
raw = Base64Url.decode(jwk.k!); | ||
} | ||
else | ||
raw = new Uint8Array(keyData as Uint8Array); | ||
const key = new CryptoKey(); | ||
key.algorithm = algorithm; | ||
key.type = "secret"; | ||
key.usages = keyUsages; | ||
key.key = raw; | ||
resolve(key); | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
let raw: Uint8Array; | ||
if (format.toLowerCase() === "jwk") { | ||
const jwk = keyData as JsonWebKey; | ||
raw = Base64Url.decode(jwk.k!); | ||
} | ||
else | ||
raw = new Uint8Array(keyData as Uint8Array); | ||
const key = new CryptoKey(); | ||
key.algorithm = algorithm; | ||
key.type = "secret"; | ||
key.usages = keyUsages; | ||
key.key = raw; | ||
return key; | ||
}); | ||
} | ||
} | ||
} | ||
import { Crypto } from "../crypto"; |
@@ -1,12 +0,4 @@ | ||
import { WebCryptoError } from "webcrypto-core"; | ||
import { SubtleCrypto } from "./subtle"; | ||
import { nativeCrypto } from "./init"; | ||
export class LinerError extends WebCryptoError { | ||
code = 10; | ||
static MODULE_NOT_FOUND = "Module '%1' is not found. Download it from %2.\nOnly hash algorithms supported by the user agent will be supported."; | ||
static UNSUPPORTED_ALGORITHM = "Unsupported algorithm '%1'"; | ||
} | ||
export class Crypto { | ||
@@ -13,0 +5,0 @@ |
import { BaseCrypto, AlgorithmNames, AlgorithmError, Base64Url } from "webcrypto-core"; | ||
import { LinerError } from "../crypto"; | ||
import { LinerError } from "../error"; | ||
import { CryptoKey, CryptoKeyPair } from "../key"; | ||
import { string2buffer, buffer2string, concat } from "../helper"; | ||
import * as elliptic from "elliptic"; | ||
// import * as elliptic from "elliptic"; | ||
declare let elliptic: any; | ||
@@ -65,158 +66,168 @@ | ||
static generateKey(alg: Algorithm, extractable: boolean, keyUsage: string[]) { | ||
return new Promise<CryptoKeyPair>(resolve => { | ||
this.checkModule(); | ||
const _alg: EcKeyGenParams = alg as any; | ||
const key = new elliptic.ec(_alg.namedCurve.replace("-", "").toLowerCase()); // converts name to 'p192', ... | ||
return Promise.resolve() | ||
.then(() => { | ||
this.checkModule(); | ||
const _alg: EcKeyGenParams = alg as any; | ||
const key = new elliptic.ec(_alg.namedCurve.replace("-", "").toLowerCase()); // converts name to 'p192', ... | ||
// set key params | ||
const prvKey = new CryptoKey(); | ||
const pubKey = new CryptoKey(); | ||
prvKey.key = pubKey.key = key.genKeyPair(); | ||
prvKey.algorithm = pubKey.algorithm = _alg; | ||
prvKey.extractable = extractable; | ||
pubKey.extractable = true; | ||
prvKey.type = "private"; | ||
pubKey.type = "public"; | ||
if (alg.name === AlgorithmNames.EcDSA) { | ||
prvKey.usages = ["sign"]; | ||
pubKey.usages = ["verify"]; | ||
} | ||
else if (alg.name === AlgorithmNames.EcDH) { | ||
prvKey.usages = pubKey.usages = ["deriveKey", "deriveBits"]; | ||
} | ||
resolve({ | ||
privateKey: prvKey, | ||
publicKey: pubKey | ||
// set key params | ||
const prvKey = new CryptoKey(); | ||
const pubKey = new CryptoKey(); | ||
prvKey.key = pubKey.key = key.genKeyPair(); | ||
prvKey.algorithm = pubKey.algorithm = _alg; | ||
prvKey.extractable = extractable; | ||
pubKey.extractable = true; | ||
prvKey.type = "private"; | ||
pubKey.type = "public"; | ||
if (alg.name === AlgorithmNames.EcDSA) { | ||
prvKey.usages = ["sign"]; | ||
pubKey.usages = ["verify"]; | ||
} | ||
else if (alg.name === AlgorithmNames.EcDH) { | ||
prvKey.usages = pubKey.usages = ["deriveKey", "deriveBits"]; | ||
} | ||
return { | ||
privateKey: prvKey, | ||
publicKey: pubKey | ||
}; | ||
}); | ||
}); | ||
} | ||
static sign(algorithm: Algorithm, key: CryptoKey, data: Uint8Array): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
const _alg: EcdsaParams = algorithm as any; | ||
return Promise.resolve() | ||
.then(() => { | ||
const _alg: EcdsaParams = algorithm as any; | ||
// get digest | ||
(self.crypto.subtle.digest(_alg.hash, data) as Promise<ArrayBuffer>) | ||
.then(hash => { | ||
const array = b2a(hash); | ||
const signature = key.key.sign(array); | ||
const hexSignature = buffer2hex(signature.r.toArray(), true) + buffer2hex(signature.s.toArray(), true); | ||
resolve(hex2buffer(hexSignature).buffer); | ||
}) | ||
.catch(reject); | ||
}); | ||
// get digest | ||
let crypto = new Crypto(); | ||
return crypto.subtle.digest(_alg.hash, data); | ||
}) | ||
.then(hash => { | ||
const array = b2a(hash); | ||
const signature = key.key.sign(array); | ||
const hexSignature = buffer2hex(signature.r.toArray(), true) + buffer2hex(signature.s.toArray(), true); | ||
return hex2buffer(hexSignature).buffer; | ||
}); | ||
} | ||
static verify(algorithm: Algorithm, key: CryptoKey, signature: Uint8Array, data: Uint8Array): PromiseLike<boolean> { | ||
return new Promise((resolve, reject) => { | ||
const _alg: EcdsaParams = algorithm as any; | ||
const sig = { | ||
r: signature.slice(0, signature.byteLength / 2), | ||
s: signature.slice(signature.byteLength / 2) | ||
}; | ||
// get digest | ||
(self.crypto.subtle.digest(_alg.hash, data) as Promise<ArrayBuffer>) | ||
.then(hash => { | ||
const array = b2a(hash); | ||
resolve(key.key.verify(array, sig)); | ||
}) | ||
.catch(reject); | ||
}); | ||
let sig: { r: Uint8Array, s: Uint8Array }; | ||
return Promise.resolve() | ||
.then(() => { | ||
const _alg: EcdsaParams = algorithm as any; | ||
sig = { | ||
r: signature.slice(0, signature.byteLength / 2), | ||
s: signature.slice(signature.byteLength / 2) | ||
}; | ||
// get digest | ||
let crypto = new Crypto(); | ||
return crypto.subtle.digest(_alg.hash, data); | ||
}) | ||
.then(hash => { | ||
const array = b2a(hash); | ||
return (key.key.verify(array, sig)); | ||
}); | ||
} | ||
static deriveKey(algorithm: EcdhKeyDeriveParams, baseKey: CryptoKey, derivedKeyType: AesKeyGenParams, extractable: boolean, keyUsages: string[]): PromiseLike<CryptoKey> { | ||
return new Promise((resolve, reject) => { | ||
this.deriveBits(algorithm, baseKey, derivedKeyType.length) | ||
.then((bits: ArrayBuffer) => { | ||
return self.crypto.subtle.importKey("raw", new Uint8Array(bits), derivedKeyType, extractable, keyUsages); | ||
}) | ||
.then(resolve, reject); | ||
}); | ||
return Promise.resolve() | ||
.then(() => | ||
this.deriveBits(algorithm, baseKey, derivedKeyType.length) | ||
) | ||
.then((bits: ArrayBuffer) => { | ||
let crypto = new Crypto(); | ||
return crypto.subtle.importKey("raw", new Uint8Array(bits), derivedKeyType, extractable, keyUsages); | ||
}); | ||
} | ||
static deriveBits(algorithm: EcdhKeyDeriveParams, baseKey: CryptoKey, length: number): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
let promise = (Promise as any).resolve(null); | ||
const shared = baseKey.key.derive((algorithm.public as CryptoKey).key.getPublic()); | ||
let array = new Uint8Array(shared.toArray()); | ||
// Padding | ||
let len = array.length; | ||
len = len > 32 ? len > 48 ? 66 : 48 : 32; | ||
if (array.length < len) | ||
array = concat(new Uint8Array(len - array.length), array); | ||
const buf = array.slice(0, length / 8).buffer; | ||
resolve(buf); | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
let promise = (Promise as any).resolve(null); | ||
const shared = baseKey.key.derive((algorithm.public as CryptoKey).key.getPublic()); | ||
let array = new Uint8Array(shared.toArray()); | ||
// Padding | ||
let len = array.length; | ||
len = (len > 32 ? (len > 48 ? 66 : 48) : 32); | ||
if (array.length < len) | ||
array = concat(new Uint8Array(len - array.length), array); | ||
const buf = array.slice(0, length / 8).buffer; | ||
return buf; | ||
}); | ||
} | ||
static exportKey(format: string, key: EcCryptoKey): PromiseLike<JsonWebKey | ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
const ecKey = key.key; | ||
if (format.toLowerCase() === "jwk") { | ||
let hexPub = ecKey.getPublic("hex").slice(2); // ignore first '04' | ||
const hexX = hexPub.slice(0, hexPub.length / 2); | ||
const hexY = hexPub.slice(hexPub.length / 2, hexPub.length); | ||
if (key.type === "public") { | ||
// public | ||
let jwk: JsonWebKey = { | ||
crv: (key.algorithm as EcKeyGenParams).namedCurve, | ||
ext: key.extractable, | ||
x: Base64Url.encode(hex2buffer(hexX, true)), | ||
y: Base64Url.encode(hex2buffer(hexY, true)), | ||
key_ops: [], | ||
kty: "EC" | ||
}; | ||
resolve(jwk); | ||
return Promise.resolve() | ||
.then(() => { | ||
const ecKey = key.key; | ||
if (format.toLowerCase() === "jwk") { | ||
let hexPub = ecKey.getPublic("hex").slice(2); // ignore first '04' | ||
const hexX = hexPub.slice(0, hexPub.length / 2); | ||
const hexY = hexPub.slice(hexPub.length / 2, hexPub.length); | ||
if (key.type === "public") { | ||
// public | ||
let jwk: JsonWebKey = { | ||
crv: (key.algorithm as EcKeyGenParams).namedCurve, | ||
ext: key.extractable, | ||
x: Base64Url.encode(hex2buffer(hexX, true)), | ||
y: Base64Url.encode(hex2buffer(hexY, true)), | ||
key_ops: key.usages, | ||
kty: "EC" | ||
}; | ||
return jwk; | ||
} | ||
else { | ||
// private | ||
let jwk: JsonWebKey = { | ||
crv: (key.algorithm as EcKeyGenParams).namedCurve, | ||
ext: key.extractable, | ||
d: Base64Url.encode(hex2buffer(ecKey.getPrivate("hex"), true)), | ||
x: Base64Url.encode(hex2buffer(hexX, true)), | ||
y: Base64Url.encode(hex2buffer(hexY, true)), | ||
key_ops: key.usages, | ||
kty: "EC" | ||
}; | ||
return jwk; | ||
} | ||
} | ||
else { | ||
// private | ||
let jwk: JsonWebKey = { | ||
crv: (key.algorithm as EcKeyGenParams).namedCurve, | ||
ext: key.extractable, | ||
d: Base64Url.encode(hex2buffer(ecKey.getPrivate("hex"), true)), | ||
x: Base64Url.encode(hex2buffer(hexX, true)), | ||
y: Base64Url.encode(hex2buffer(hexY, true)), | ||
key_ops: key.usages, | ||
kty: "EC" | ||
}; | ||
resolve(jwk); | ||
throw new LinerError(`Format '${format}' is not implemented`); | ||
} | ||
} | ||
else { | ||
throw new LinerError(`Format '${format}' is not implemented`); | ||
} | ||
}); | ||
}); | ||
} | ||
static importKey(format: string, keyData: JsonWebKey | BufferSource, algorithm: string | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | DhImportKeyParams, extractable: boolean, keyUsages: string[]): PromiseLike<CryptoKey> { | ||
return new Promise((resolve, reject) => { | ||
const key: EcCryptoKey = new CryptoKey(); | ||
key.algorithm = algorithm; | ||
if (format.toLowerCase() === "jwk") { | ||
const ecKey = new elliptic.ec((keyData as JsonWebKey).crv!.replace("-", "").toLowerCase()); | ||
if ((keyData as JsonWebKey).d) { | ||
// Private key | ||
key.key = ecKey.keyFromPrivate(Base64Url.decode((keyData as JsonWebKey).d!)); | ||
key.type = "private"; | ||
} | ||
else { | ||
// Public key | ||
let bufferPubKey = concat( | ||
new Uint8Array([4]), | ||
Base64Url.decode((keyData as JsonWebKey).x!), | ||
Base64Url.decode((keyData as JsonWebKey).y!) | ||
); | ||
const hexPubKey = buffer2hex(bufferPubKey); | ||
return Promise.resolve() | ||
.then(() => { | ||
const key: EcCryptoKey = new CryptoKey(); | ||
key.algorithm = algorithm; | ||
if (format.toLowerCase() === "jwk") { | ||
const ecKey = new elliptic.ec((keyData as JsonWebKey).crv!.replace("-", "").toLowerCase()); | ||
if ((keyData as JsonWebKey).d) { | ||
// Private key | ||
key.key = ecKey.keyFromPrivate(Base64Url.decode((keyData as JsonWebKey).d!)); | ||
key.type = "private"; | ||
} | ||
else { | ||
// Public key | ||
let bufferPubKey = concat( | ||
new Uint8Array([4]), | ||
Base64Url.decode((keyData as JsonWebKey).x!), | ||
Base64Url.decode((keyData as JsonWebKey).y!) | ||
); | ||
const hexPubKey = buffer2hex(bufferPubKey); | ||
key.key = ecKey.keyFromPublic(hexPubKey, "hex"); | ||
key.type = "public"; | ||
key.key = ecKey.keyFromPublic(hexPubKey, "hex"); | ||
key.type = "public"; | ||
} | ||
} | ||
} | ||
else | ||
throw new LinerError(`Format '${format}' is not implemented`); | ||
key.extractable = extractable; | ||
key.usages = keyUsages; | ||
resolve(key); | ||
}); | ||
else | ||
throw new LinerError(`Format '${format}' is not implemented`); | ||
key.extractable = extractable; | ||
key.usages = keyUsages; | ||
return key; | ||
}); | ||
} | ||
} | ||
} | ||
import { Crypto } from "../crypto"; |
@@ -17,11 +17,5 @@ export let Browser = { | ||
}; | ||
if (typeof window !== "object" && typeof self !== void "object") | ||
return { | ||
name: "NodeJS", | ||
version: process.version | ||
}; | ||
const userAgent = self.navigator.userAgent; | ||
let reg: RegExpExecArray | null; | ||
let reg: string[] | null; | ||
if (reg = /edge\/([\d\.]+)/i.exec(userAgent)) { | ||
@@ -33,2 +27,5 @@ res.name = Browser.Edge; | ||
res.version = /msie ([\d\.]+)/i.exec(userAgent) ![1]; | ||
} else if (/Trident/i.test(userAgent)) { | ||
res.name = Browser.IE; | ||
res.version = /rv:([\d\.]+)/i.exec(userAgent) ![1]; | ||
} else if (/chrome/i.test(userAgent)) { | ||
@@ -35,0 +32,0 @@ res.name = Browser.Chrome; |
export * from "./init"; | ||
export * from "./main"; | ||
export * from "./crypto"; |
@@ -0,4 +1,18 @@ | ||
import { LinerError } from "./error"; | ||
let _w: any; | ||
if (typeof self === "undefined") { | ||
_w = { crypto: { subtle: {} } }; | ||
const crypto = require("crypto"); | ||
_w = { | ||
crypto: { | ||
subtle: {}, | ||
getRandomValues: (array: ArrayBufferView) => { | ||
let buf = array.buffer; | ||
let uint8buf = new Uint8Array(buf); | ||
const rnd = crypto.randomBytes(uint8buf.length); | ||
rnd.forEach((octet: number, index: number) => uint8buf[index] = octet); | ||
return array; | ||
} | ||
} | ||
}; | ||
} | ||
@@ -9,2 +23,47 @@ else | ||
export const nativeCrypto: NativeCrypto = _w.msCrypto || _w.crypto; | ||
export const nativeSubtle: NativeSubtleCrypto = nativeCrypto.subtle || (nativeCrypto as any).webkitSubtle; | ||
export const nativeSubtle: NativeSubtleCrypto = nativeCrypto.subtle || (nativeCrypto as any).webkitSubtle; | ||
function WrapFunction(subtle: any, name: string) { | ||
const fn = subtle[name]; | ||
subtle[name] = function () { | ||
const _args = arguments; | ||
return new Promise((resolve, reject) => { | ||
let op: any = fn.apply(subtle, _args); | ||
op.oncomplete = (e: any) => { | ||
console.log("Complited"); | ||
resolve(e.target.result); | ||
}; | ||
op.onerror = (e: any) => { | ||
console.log("Error"); | ||
reject(`Error on running '${name}' function`); | ||
}; | ||
}); | ||
}; | ||
} | ||
if (_w.msCrypto) { | ||
if (!_w.Promise) | ||
throw new LinerError(LinerError.MODULE_NOT_FOUND, "Promise", "https://www.promisejs.org"); | ||
WrapFunction(nativeSubtle, "generateKey"); | ||
WrapFunction(nativeSubtle, "digest"); | ||
WrapFunction(nativeSubtle, "sign"); | ||
WrapFunction(nativeSubtle, "verify"); | ||
WrapFunction(nativeSubtle, "encrypt"); | ||
WrapFunction(nativeSubtle, "decrypt"); | ||
WrapFunction(nativeSubtle, "importKey"); | ||
WrapFunction(nativeSubtle, "exportKey"); | ||
WrapFunction(nativeSubtle, "wrapKey"); | ||
WrapFunction(nativeSubtle, "unwrapKey"); | ||
WrapFunction(nativeSubtle, "deriveKey"); | ||
WrapFunction(nativeSubtle, "deriveBits"); | ||
} | ||
// fix: Math.imul for IE | ||
if (!(Math as any).imul) | ||
(Math as any).imul = function imul(a: number, b: number) { | ||
let ah = (a >>> 16) & 0xffff; | ||
let al = a & 0xffff; | ||
let bh = (b >>> 16) & 0xffff; | ||
let bl = b & 0xffff; | ||
return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0); | ||
}; |
import { BaseCrypto, AlgorithmNames, AlgorithmError, Base64Url } from "webcrypto-core"; | ||
import { LinerError } from "../crypto"; | ||
import { LinerError } from "../error"; | ||
import { CryptoKey, CryptoKeyPair } from "../key"; | ||
import { string2buffer, buffer2string, concat } from "../helper"; | ||
import * as asmCrypto from "asmcrypto.js"; | ||
@@ -34,212 +33,220 @@ interface RsaCryptoKey extends CryptoKey { | ||
static generateKey(alg: RsaKeyGenParams, extractable: boolean, keyUsage: string[]): PromiseLike<CryptoKeyPair> { | ||
return new Promise<CryptoKeyPair>(resolve => { | ||
this.checkModule(); | ||
return Promise.resolve() | ||
.then(() => { | ||
this.checkModule(); | ||
const pubExp = alg.publicExponent[0] === 3 ? 3 : 65537; | ||
const rsaKey = asmCrypto.RSA.generateKey(alg.modulusLength, pubExp); | ||
const privateKey: RsaCryptoKey = new CryptoKey(); | ||
const publicKey: RsaCryptoKey = new CryptoKey(); | ||
privateKey.key = publicKey.key = rsaKey; | ||
privateKey.algorithm = publicKey.algorithm = alg; | ||
privateKey.extractable = extractable; | ||
publicKey.extractable = true; | ||
privateKey.type = "private"; | ||
publicKey.type = "public"; | ||
switch (alg.name.toLowerCase()) { | ||
case AlgorithmNames.RsaOAEP.toLowerCase(): | ||
privateKey.usages = this.filterUsages(["decrypt", "unwrapKey"], keyUsage); | ||
publicKey.usages = this.filterUsages(["encrypt", "wrapKey"], keyUsage); | ||
break; | ||
case AlgorithmNames.RsaPSS.toLowerCase(): | ||
privateKey.usages = this.filterUsages(["sign"], keyUsage); | ||
publicKey.usages = this.filterUsages(["verify"], keyUsage); | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, alg.name); | ||
} | ||
resolve({ privateKey, publicKey }); | ||
}); | ||
const pubExp = alg.publicExponent[0] === 3 ? 3 : 65537; | ||
const rsaKey = asmCrypto.RSA.generateKey(alg.modulusLength, pubExp); | ||
const privateKey: RsaCryptoKey = new CryptoKey(); | ||
const publicKey: RsaCryptoKey = new CryptoKey(); | ||
privateKey.key = publicKey.key = rsaKey; | ||
privateKey.algorithm = publicKey.algorithm = alg; | ||
privateKey.extractable = extractable; | ||
publicKey.extractable = true; | ||
privateKey.type = "private"; | ||
publicKey.type = "public"; | ||
switch (alg.name.toLowerCase()) { | ||
case AlgorithmNames.RsaOAEP.toLowerCase(): | ||
privateKey.usages = this.filterUsages(["decrypt", "unwrapKey"], keyUsage); | ||
publicKey.usages = this.filterUsages(["encrypt", "wrapKey"], keyUsage); | ||
break; | ||
case AlgorithmNames.RsaPSS.toLowerCase(): | ||
privateKey.usages = this.filterUsages(["sign"], keyUsage); | ||
publicKey.usages = this.filterUsages(["verify"], keyUsage); | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, alg.name); | ||
} | ||
return { privateKey, publicKey }; | ||
}); | ||
} | ||
static sign(algorithm: Algorithm, key: RsaCryptoKey, data: Uint8Array): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaPSS.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaPssParams = algorithm as any; | ||
let sign: typeof asmCrypto.RSA_PSS_SHA1.sign; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
sign = asmCrypto.RSA_PSS_SHA1.sign; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
sign = asmCrypto.RSA_PSS_SHA256.sign; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, key.algorithm.name); | ||
} | ||
resolve(sign(data, key.key, _alg.saltLength).buffer); | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaPSS.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaPssParams = algorithm as any; | ||
let sign: typeof asmCrypto.RSA_PSS_SHA1.sign; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
sign = asmCrypto.RSA_PSS_SHA1.sign; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
sign = asmCrypto.RSA_PSS_SHA256.sign; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, key.algorithm.name); | ||
} | ||
return sign(data, key.key, _alg.saltLength).buffer; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
} | ||
static verify(algorithm: Algorithm, key: RsaCryptoKey, signature: Uint8Array, data: Uint8Array): PromiseLike<boolean> { | ||
return new Promise((resolve, reject) => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaPSS.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaPssParams = algorithm as any; | ||
let verify: typeof asmCrypto.RSA_PSS_SHA1.verify; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
verify = asmCrypto.RSA_PSS_SHA1.verify; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
verify = asmCrypto.RSA_PSS_SHA256.verify; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, key.algorithm.name); | ||
} | ||
resolve(verify(signature, data, key.key, _alg.saltLength)); | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaPSS.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaPssParams = algorithm as any; | ||
let verify: typeof asmCrypto.RSA_PSS_SHA1.verify; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
verify = asmCrypto.RSA_PSS_SHA1.verify; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
verify = asmCrypto.RSA_PSS_SHA256.verify; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, key.algorithm.name); | ||
} | ||
return verify(signature, data, key.key, _alg.saltLength); | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
} | ||
static encrypt(algorithm: Algorithm, key: RsaCryptoKey, data: Uint8Array): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaOAEP.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaOaepParams = algorithm as any; | ||
let encrypt: typeof asmCrypto.RSA_OAEP_SHA1.encrypt; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
encrypt = asmCrypto.RSA_OAEP_SHA1.encrypt; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
encrypt = asmCrypto.RSA_OAEP_SHA256.encrypt; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, `${keyAlg.name} ${(keyAlg.hash as Algorithm).name}`); | ||
} | ||
resolve(encrypt(data, key.key, _alg.label)); | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaOAEP.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaOaepParams = algorithm as any; | ||
let encrypt: typeof asmCrypto.RSA_OAEP_SHA1.encrypt; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
encrypt = asmCrypto.RSA_OAEP_SHA1.encrypt; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
encrypt = asmCrypto.RSA_OAEP_SHA256.encrypt; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, `${keyAlg.name} ${(keyAlg.hash as Algorithm).name}`); | ||
} | ||
return encrypt(data, key.key, _alg.label); | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
} | ||
static decrypt(algorithm: Algorithm, key: RsaCryptoKey, data: Uint8Array): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaOAEP.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaOaepParams = algorithm as any; | ||
let decrypt: typeof asmCrypto.RSA_OAEP_SHA1.decrypt; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
decrypt = asmCrypto.RSA_OAEP_SHA1.decrypt; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
decrypt = asmCrypto.RSA_OAEP_SHA256.decrypt; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, `${keyAlg.name} ${(keyAlg.hash as Algorithm).name}`); | ||
} | ||
resolve(decrypt(data, key.key, _alg.label)); | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
switch (algorithm.name.toLowerCase()) { | ||
case AlgorithmNames.RsaOAEP.toLowerCase(): | ||
let keyAlg: RsaHashedKeyGenParams = key.algorithm as any; | ||
let _alg: RsaOaepParams = algorithm as any; | ||
let decrypt: typeof asmCrypto.RSA_OAEP_SHA1.decrypt; | ||
switch ((keyAlg.hash as Algorithm).name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
decrypt = asmCrypto.RSA_OAEP_SHA1.decrypt; | ||
break; | ||
case AlgorithmNames.Sha256: | ||
decrypt = asmCrypto.RSA_OAEP_SHA256.decrypt; | ||
break; | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, `${keyAlg.name} ${(keyAlg.hash as Algorithm).name}`); | ||
} | ||
return decrypt(data, key.key, _alg.label); | ||
default: | ||
throw new LinerError(LinerError.UNSUPPORTED_ALGORITHM, algorithm.name); | ||
} | ||
}); | ||
} | ||
static wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: Algorithm): PromiseLike<ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
self.crypto.subtle.exportKey(format, key) | ||
.then((data: any) => { | ||
let raw: Uint8Array; | ||
if (!(data instanceof ArrayBuffer)) { | ||
// JWK | ||
raw = string2buffer(JSON.stringify(data)); | ||
} | ||
else { | ||
// ArrayBuffer | ||
raw = new Uint8Array(data); | ||
} | ||
return self.crypto.subtle.encrypt(wrapAlgorithm, wrappingKey, raw); | ||
}) | ||
.then(resolve, reject); | ||
}); | ||
let crypto: Crypto; | ||
return Promise.resolve() | ||
.then(() => { | ||
crypto = new Crypto(); | ||
return crypto.subtle.exportKey(format, key); | ||
}) | ||
.then((data: any) => { | ||
let raw: Uint8Array; | ||
if (!(data instanceof ArrayBuffer)) { | ||
// JWK | ||
raw = string2buffer(JSON.stringify(data)); | ||
} | ||
else { | ||
// ArrayBuffer | ||
raw = new Uint8Array(data); | ||
} | ||
return crypto.subtle.encrypt(wrapAlgorithm, wrappingKey, raw); | ||
}); | ||
} | ||
static unwrapKey(format: string, wrappedKey: Uint8Array, unwrappingKey: CryptoKey, unwrapAlgorithm: Algorithm, unwrappedKeyAlgorithm: Algorithm, extractable: boolean, keyUsages: string[]): PromiseLike<CryptoKey> { | ||
return new Promise((resolve, reject) => { | ||
self.crypto.subtle.decrypt(unwrapAlgorithm, unwrappingKey, wrappedKey) | ||
.then((data: any) => { | ||
let _data: any; | ||
if (format.toLowerCase() === "jwk") | ||
_data = JSON.parse(buffer2string(new Uint8Array(data))); | ||
else | ||
_data = new Uint8Array(data); | ||
return self.crypto.subtle.importKey(format, _data, unwrappedKeyAlgorithm, extractable, keyUsages); | ||
}) | ||
.then(resolve, reject); | ||
}); | ||
let crypto: Crypto; | ||
return Promise.resolve() | ||
.then(() => { | ||
crypto = new Crypto(); | ||
return crypto.subtle.decrypt(unwrapAlgorithm, unwrappingKey, wrappedKey); | ||
}) | ||
.then((data: any) => { | ||
let _data: any; | ||
if (format.toLowerCase() === "jwk") | ||
_data = JSON.parse(buffer2string(new Uint8Array(data))); | ||
else | ||
_data = new Uint8Array(data); | ||
return crypto.subtle.importKey(format, _data, unwrappedKeyAlgorithm, extractable, keyUsages); | ||
}); | ||
} | ||
static alg2jwk(alg: Algorithm) { | ||
const hash = (alg as any).hash as Algorithm; | ||
const hashSize = /(\d+)/.exec(hash.name) ![1]; | ||
switch (alg.name!.toUpperCase()) { | ||
case AlgorithmNames.RsaOAEP.toUpperCase(): | ||
return `RSA-OAEP${hashSize === "1" ? "" : `-${hashSize}`}`; | ||
case AlgorithmNames.RsaPSS.toUpperCase(): | ||
return `PS${hashSize}`; | ||
case AlgorithmNames.RsaSSA.toUpperCase(): | ||
return `RS${hashSize}`; | ||
default: | ||
throw new AlgorithmError(AlgorithmError.UNSUPPORTED_ALGORITHM, alg.name); | ||
} | ||
} | ||
static jwk2alg(alg: string): Algorithm { | ||
throw new Error("Not implemented"); | ||
} | ||
static exportKey(format: string, key: RsaCryptoKey): PromiseLike<JsonWebKey | ArrayBuffer> { | ||
return new Promise((resolve, reject) => { | ||
if (format.toLowerCase() === "jwk") { | ||
const jwk: JsonWebKey = { | ||
kty: "RSA", | ||
ext: true, | ||
key_ops: key.usages | ||
}; | ||
const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash as Algorithm; | ||
const hashSize = /(\d+)/.exec(hash.name) ![1]; | ||
switch (key.algorithm.name!.toUpperCase()) { | ||
case AlgorithmNames.RsaOAEP.toUpperCase(): | ||
jwk.alg = `RSA-OAEP${hashSize === "1" ? "" : `-${hashSize}`}`; | ||
break; | ||
case AlgorithmNames.RsaPSS.toUpperCase(): | ||
jwk.alg = `PS${hashSize}`; | ||
break; | ||
case AlgorithmNames.RsaSSA.toUpperCase(): | ||
jwk.alg = `RS${hashSize}`; | ||
break; | ||
default: | ||
throw new AlgorithmError(AlgorithmError.UNSUPPORTED_ALGORITHM, key.algorithm.name); | ||
return Promise.resolve() | ||
.then(() => { | ||
if (format.toLowerCase() === "jwk") { | ||
const jwk: JsonWebKey = { | ||
kty: "RSA", | ||
ext: true, | ||
key_ops: key.usages | ||
}; | ||
jwk.alg = this.alg2jwk(key.algorithm as Algorithm); | ||
jwk.n = Base64Url.encode(removeLeadingZero(key.key[0])); | ||
jwk.e = Base64Url.encode(removeLeadingZero(key.key[1])); | ||
if (key.type === "private") { | ||
jwk.d = Base64Url.encode(removeLeadingZero(key.key[2])); | ||
jwk.p = Base64Url.encode(removeLeadingZero(key.key[3])); | ||
jwk.q = Base64Url.encode(removeLeadingZero(key.key[4])); | ||
jwk.dp = Base64Url.encode(removeLeadingZero(key.key[5])); | ||
jwk.dq = Base64Url.encode(removeLeadingZero(key.key[6])); | ||
jwk.qi = Base64Url.encode(removeLeadingZero(key.key[7])); | ||
} | ||
return jwk; | ||
} | ||
jwk.n = Base64Url.encode(removeLeadingZero(key.key[0])); | ||
jwk.e = Base64Url.encode(removeLeadingZero(key.key[1])); | ||
if (key.type === "private") { | ||
jwk.d = Base64Url.encode(removeLeadingZero(key.key[2])); | ||
jwk.p = Base64Url.encode(removeLeadingZero(key.key[3])); | ||
jwk.q = Base64Url.encode(removeLeadingZero(key.key[4])); | ||
jwk.dp = Base64Url.encode(removeLeadingZero(key.key[5])); | ||
jwk.dq = Base64Url.encode(removeLeadingZero(key.key[6])); | ||
jwk.qi = Base64Url.encode(removeLeadingZero(key.key[7])); | ||
else { | ||
throw new LinerError(LinerError.NOT_SUPPORTED); | ||
} | ||
resolve(jwk); | ||
} | ||
else { | ||
throw new LinerError(LinerError.NOT_SUPPORTED); | ||
} | ||
}); | ||
}); | ||
} | ||
static importKey(format: string, keyData: JsonWebKey | Uint8Array, algorithm: Algorithm, extractable: boolean, keyUsages: string[]): PromiseLike<CryptoKey> { | ||
return new Promise((resolve, reject) => { | ||
return Promise.resolve() | ||
.then(() => { | ||
let raw: Uint8Array; | ||
@@ -266,9 +273,10 @@ let jwk: JsonWebKey; | ||
key.type = "public"; | ||
resolve(key); | ||
return key; | ||
} | ||
else | ||
throw new LinerError(LinerError.NOT_SUPPORTED); | ||
resolve(key); | ||
}); | ||
} | ||
} | ||
} | ||
import { Crypto } from "../crypto"; |
import { BaseCrypto, AlgorithmNames, AlgorithmError, Base64Url } from "webcrypto-core"; | ||
import { LinerError } from "../crypto"; | ||
import { LinerError } from "../error"; | ||
import { CryptoKey, CryptoKeyPair } from "../key"; | ||
import { string2buffer, buffer2string, concat } from "../helper"; | ||
import * as asmCrypto from "asmcrypto.js"; | ||
export class ShaCrypto extends BaseCrypto { | ||
static digest(alg: Algorithm, message: Uint8Array) { | ||
return new Promise<ArrayBuffer>(resolve => { | ||
if (typeof asmCrypto === "undefined") | ||
throw new LinerError(LinerError.MODULE_NOT_FOUND, "asmCrypto", "https://github.com/vibornoff/asmcrypto.js"); | ||
switch (alg.name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
resolve(asmCrypto.SHA1.bytes(message).buffer); | ||
break; | ||
case AlgorithmNames.Sha256: | ||
resolve(asmCrypto.SHA256.bytes(message).buffer); | ||
break; | ||
default: | ||
throw new LinerError(`Not supported algorithm '${alg.name}'`); | ||
} | ||
}); | ||
return Promise.resolve() | ||
.then(() => { | ||
if (typeof asmCrypto === "undefined") | ||
throw new LinerError(LinerError.MODULE_NOT_FOUND, "asmCrypto", "https://github.com/vibornoff/asmcrypto.js"); | ||
switch (alg.name.toUpperCase()) { | ||
case AlgorithmNames.Sha1: | ||
return asmCrypto.SHA1.bytes(message).buffer; | ||
case AlgorithmNames.Sha256: | ||
return asmCrypto.SHA256.bytes(message).buffer; | ||
default: | ||
throw new LinerError(`Not supported algorithm '${alg.name}'`); | ||
} | ||
}); | ||
} | ||
} |
@@ -8,3 +8,4 @@ // Core | ||
import { nativeSubtle } from "./init"; | ||
import { LinerError } from "./crypto"; | ||
import { LinerError } from "./error"; | ||
import { Crypto } from "./crypto"; | ||
import { CryptoKey, CryptoKeyPair } from "./key"; | ||
@@ -24,16 +25,19 @@ import { string2buffer, buffer2string, concat, Browser, BrowserInfo, assign } from "./helper"; | ||
function PrepareKey(key: CryptoKey, subtle: typeof BaseCrypto): PromiseLike<CryptoKey> { | ||
let promise = Promise.resolve(key); | ||
if (!key.key) | ||
if (!key.extractable) { | ||
throw new LinerError("'key' is Native CryptoKey. It can't be converted to JS CryptoKey"); | ||
} | ||
else { | ||
promise = promise.then(() => | ||
self.crypto.subtle.exportKey("jwk", key) | ||
) | ||
.then((jwk: any) => | ||
subtle.importKey("jwk", jwk, key.algorithm as Algorithm, true, key.usages) | ||
); | ||
} | ||
return promise; | ||
return Promise.resolve() | ||
.then(() => { | ||
if (!key.key) { | ||
if (!key.extractable) { | ||
throw new LinerError("'key' is Native CryptoKey. It can't be converted to JS CryptoKey"); | ||
} | ||
else { | ||
let crypto = new Crypto(); | ||
return crypto.subtle.exportKey("jwk", key) | ||
.then((jwk: any) => | ||
subtle.importKey("jwk", jwk, key.algorithm as Algorithm, true, key.usages) | ||
); | ||
} | ||
} | ||
else | ||
return key; | ||
}); | ||
} | ||
@@ -63,9 +67,5 @@ | ||
if (keys) { | ||
if ("keyUsage" in keys || ((keys as IE).privateKey && "keyUsage" in keys)) { | ||
let _keys: IE = keys; | ||
if (!_keys.privateKey) | ||
_keys.usages = keyUsages; | ||
} | ||
FixCryptoKeyUsages(keys, keyUsages); | ||
SetHashAlgorithm(_alg, keys); | ||
return new Promise(resolve => resolve(keys)); | ||
return keys; | ||
} | ||
@@ -113,3 +113,3 @@ let Class: typeof BaseCrypto; | ||
.then((digest: ArrayBuffer) => { | ||
if (digest) return new Promise(resolve => resolve(digest)); | ||
if (digest) return digest; | ||
return ShaCrypto.digest(_alg, _data); | ||
@@ -128,3 +128,6 @@ }); | ||
GetHashAlgorithm(_alg, key); | ||
const _alg2 = GetHashAlgorithm(key); | ||
if (_alg2) { | ||
args[0] = assign(_alg, _alg2); | ||
} | ||
try { | ||
@@ -141,3 +144,3 @@ return nativeSubtle.sign.apply(nativeSubtle, args) | ||
.then((signature: ArrayBuffer) => { | ||
if (signature) return new Promise(resolve => resolve(signature)); | ||
if (signature) return signature; | ||
let Class: typeof BaseCrypto; | ||
@@ -170,3 +173,6 @@ switch (_alg.name.toLowerCase()) { | ||
GetHashAlgorithm(_alg, key); | ||
const _alg2 = GetHashAlgorithm(key); | ||
if (_alg2) { | ||
args[0] = assign(_alg, _alg2); | ||
} | ||
try { | ||
@@ -183,3 +189,3 @@ return nativeSubtle.verify.apply(nativeSubtle, args) | ||
.then((result: boolean) => { | ||
if (typeof result === "boolean") return new Promise(resolve => resolve(result)); | ||
if (typeof result === "boolean") return result; | ||
let Class: typeof BaseCrypto; | ||
@@ -221,3 +227,3 @@ switch (_alg.name.toLowerCase()) { | ||
.then((bits: ArrayBuffer) => { | ||
if (bits) return new Promise(resolve => resolve(bits)); | ||
if (bits) return bits; | ||
let Class: typeof BaseCrypto; | ||
@@ -255,3 +261,6 @@ switch (_alg.name.toLowerCase()) { | ||
.then((key: CryptoKey) => { | ||
if (key) return new Promise(resolve => resolve(key)); | ||
if (key) { | ||
FixCryptoKeyUsages(key, keyUsages); | ||
return key; | ||
} | ||
let Class: typeof BaseCrypto; | ||
@@ -288,4 +297,16 @@ switch (_alg.name.toLowerCase()) { | ||
}) | ||
.then((msg: ArrayBuffer) => { | ||
if (msg) return new Promise(resolve => resolve(msg)); | ||
.then((msg: any) => { | ||
if (msg) { | ||
if (BrowserInfo().name === Browser.IE && | ||
_alg.name.toUpperCase() === AlgorithmNames.AesGCM && | ||
msg.ciphertext) { | ||
// Concatinate values in IE | ||
let buf = new Uint8Array(msg.ciphertext.byteLength + msg.tag.byteLength); | ||
let count = 0; | ||
new Uint8Array(msg.ciphertext).forEach((v: number) => buf[count++] = v); | ||
new Uint8Array(msg.tag).forEach((v: number) => buf[count++] = v); | ||
msg = buf.buffer; | ||
} | ||
return Promise.resolve(msg); | ||
} | ||
let Class: typeof BaseCrypto; | ||
@@ -316,4 +337,15 @@ switch (_alg.name.toLowerCase()) { | ||
let _data2: any = _data; | ||
if (BrowserInfo().name === Browser.IE && | ||
_alg.name.toUpperCase() === AlgorithmNames.AesGCM) { | ||
// Split buffer | ||
const len = _data.byteLength - ((_alg as any).tagLength / 8); | ||
_data2 = { | ||
ciphertext: _data.buffer.slice(0, len), | ||
tag: _data.buffer.slice(len, _data.byteLength) | ||
}; | ||
} | ||
try { | ||
return nativeSubtle.decrypt.apply(nativeSubtle, args) | ||
return nativeSubtle.decrypt.call(nativeSubtle, _alg, key, _data2) | ||
.catch((e: Error) => { | ||
@@ -328,3 +360,3 @@ console.warn(`WebCrypto: native 'decrypt' for ${_alg.name} doesn't work.`, e.message || ""); | ||
.then((msg: ArrayBuffer) => { | ||
if (msg) return new Promise(resolve => resolve(msg)); | ||
if (msg) return msg; | ||
let Class: typeof BaseCrypto; | ||
@@ -365,3 +397,3 @@ switch (_alg.name.toLowerCase()) { | ||
.then((msg: ArrayBuffer) => { | ||
if (msg) return new Promise(resolve => resolve(msg)); | ||
if (msg) return msg; | ||
let Class: typeof BaseCrypto; | ||
@@ -404,4 +436,7 @@ switch (_alg.name.toLowerCase()) { | ||
}) | ||
.then((msg: ArrayBuffer) => { | ||
if (msg) return new Promise(resolve => resolve(msg)); | ||
.then((k: CryptoKey) => { | ||
if (k) { | ||
FixCryptoKeyUsages(k, keyUsages); | ||
return k; | ||
} | ||
let Class: typeof BaseCrypto; | ||
@@ -427,3 +462,2 @@ switch (_alg.name.toLowerCase()) { | ||
.then(() => { | ||
try { | ||
@@ -445,2 +479,5 @@ return nativeSubtle.exportKey.apply(nativeSubtle, args) | ||
} | ||
let alg = GetHashAlgorithm(key); | ||
if (!alg) alg = assign({}, key.algorithm); | ||
FixExportJwk(msg, alg, key.usages); | ||
return Promise.resolve(msg); | ||
@@ -481,4 +518,8 @@ } | ||
// Fix: Safari | ||
if (BrowserInfo().name === Browser.Safari) { | ||
if (BrowserInfo().name === Browser.Safari || BrowserInfo().name === Browser.IE) { | ||
// Converts JWK to ArrayBuffer | ||
if (BrowserInfo().name === Browser.IE) { | ||
keyData = assign({}, keyData); | ||
FixImportJwk(keyData); | ||
} | ||
args[1] = string2buffer(JSON.stringify(keyData)).buffer; | ||
@@ -501,6 +542,7 @@ } | ||
}) | ||
.then((msg: CryptoKey) => { | ||
if (msg) { | ||
SetHashAlgorithm(_alg, msg); | ||
return new Promise(resolve => resolve(msg)); | ||
.then((k: CryptoKey) => { | ||
if (k) { | ||
SetHashAlgorithm(_alg, k); | ||
FixCryptoKeyUsages(k, keyUsages); | ||
return Promise.resolve(k); | ||
} | ||
@@ -531,3 +573,3 @@ let Class: typeof BaseCrypto; | ||
function SetHashAlgorithm(alg: Algorithm, key: CryptoKey | CryptoKeyPair) { | ||
if ((BrowserInfo().name === Browser.Edge || BrowserInfo().name === Browser.Safari) && /^rsa/i.test(alg.name)) { | ||
if ((BrowserInfo().name === Browser.IE || BrowserInfo().name === Browser.Edge || BrowserInfo().name === Browser.Safari) && /^rsa/i.test(alg.name)) { | ||
if ((key as CryptoKeyPair).privateKey) { | ||
@@ -543,12 +585,108 @@ keys.push({ hash: (alg as any).hash, key: (key as CryptoKeyPair).privateKey }); | ||
// fix hash alg for rsa key | ||
function GetHashAlgorithm(alg: Algorithm, key: CryptoKey) { | ||
if ((BrowserInfo().name === Browser.Edge || BrowserInfo().name === Browser.Safari) && /^rsa/i.test(alg.name)) { | ||
keys.some(item => { | ||
if (item.key = key) { | ||
(alg as any).hash = item.hash; | ||
return true; | ||
function GetHashAlgorithm(key: CryptoKey) { | ||
let alg: Algorithm | null = null; | ||
keys.some(item => { | ||
if (item.key === key) { | ||
alg = assign({}, key.algorithm, { hash: item.hash }); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return alg; | ||
} | ||
// Extend Uint8Array for IE | ||
if (!Uint8Array.prototype.forEach) { | ||
(Uint8Array as any).prototype.forEach = function (cb: (value: number, index: number, array: Uint8Array) => void) { | ||
for (let i = 0; i < this.length; i++) { | ||
cb(this[i], i, this); | ||
} | ||
}; | ||
} | ||
if (!Uint8Array.prototype.slice) { | ||
(Uint8Array as any).prototype.slice = function (start: number, end: number) { | ||
return new Uint8Array(this.buffer.slice(start, end)); | ||
}; | ||
} | ||
if (!Uint8Array.prototype.filter) { | ||
(Uint8Array as any).prototype.filter = function (cb: (value: number, index: number, array: Uint8Array) => void) { | ||
let buf: number[] = []; | ||
for (let i = 0; i < this.length; i++) { | ||
if (cb(this[i], i, this)) | ||
buf.push(this[i]); | ||
} | ||
return new Uint8Array(buf); | ||
}; | ||
} | ||
function FixCryptoKeyUsages(key: CryptoKey | CryptoKeyPair, keyUsages: string[]) { | ||
const keys: CryptoKey[] = []; | ||
if ((key as CryptoKeyPair).privateKey) { | ||
keys.push((key as CryptoKeyPair).privateKey); | ||
keys.push((key as CryptoKeyPair).publicKey); | ||
} | ||
else { | ||
keys.push(key as CryptoKey); | ||
} | ||
keys.forEach((k: any) => { | ||
if ("keyUsage" in k) { | ||
k.usages = k.keyUsage || []; | ||
// add usages | ||
if (!k.usages.length) { | ||
["verify", "encrypt", "wrapKey"] | ||
.forEach(usage => { | ||
if (keyUsages.indexOf(usage) > -1 && (k.type === "public" || k.type === "secret")) | ||
k.usages.push(usage); | ||
}); | ||
["sign", "decrypt", "unwrapKey", "deriveKey", "deriveBits"] | ||
.forEach(usage => { | ||
if (keyUsages.indexOf(usage) > -1 && (k.type === "private" || k.type === "secret")) | ||
k.usages.push(usage); | ||
}); | ||
} | ||
return false; | ||
}); | ||
} | ||
}); | ||
} | ||
function FixExportJwk(jwk: any, alg: any, keyUsages: string[]) { | ||
if (alg && BrowserInfo().name === Browser.IE) { | ||
// ext | ||
if ("extractable" in jwk) { | ||
jwk.ext = jwk.extractable; | ||
delete jwk.extractable; | ||
} | ||
// add alg | ||
let CryptoClass: AlgorithmConverter | null = null; | ||
switch (alg.name.toUpperCase()) { | ||
case AlgorithmNames.RsaOAEP.toUpperCase(): | ||
case AlgorithmNames.RsaPSS.toUpperCase(): | ||
case AlgorithmNames.RsaSSA.toUpperCase(): | ||
CryptoClass = RsaCrypto; | ||
break; | ||
case AlgorithmNames.AesCBC.toUpperCase(): | ||
case AlgorithmNames.AesGCM.toUpperCase(): | ||
CryptoClass = AesCrypto; | ||
break; | ||
} | ||
if (CryptoClass && !jwk.alg) { | ||
jwk.alg = CryptoClass.alg2jwk(alg); | ||
} | ||
// add key_ops | ||
if (!("key_ops" in jwk)) | ||
jwk.key_ops = keyUsages; | ||
} | ||
} | ||
} | ||
function FixImportJwk(jwk: any) { | ||
if (BrowserInfo().name === Browser.IE) { | ||
// ext | ||
if ("ext" in jwk) { | ||
jwk.extractable = jwk.ext; | ||
delete jwk.ext; | ||
} | ||
delete jwk.key_ops; | ||
delete jwk.alg; | ||
} | ||
} |
"use strict" | ||
const path = require("path"); | ||
const webpack = require("webpack"); | ||
module.exports = { | ||
entry: "./src/index.ts", | ||
module.exports = { | ||
entry: { | ||
"webcrypto-liner.shim": "./src/shim.ts", | ||
"webcrypto-liner.lib": "./src/lib.ts", | ||
}, | ||
output: { | ||
filename: "index.js" | ||
library: "liner", | ||
path: path.join(__dirname, "dist"), | ||
filename: "[name].js" | ||
}, | ||
@@ -15,9 +21,12 @@ resolve: { | ||
loaders: [ | ||
{ test: /\.ts$/, loader: "ts-loader", exclude:path.resolve(__dirname, "node_modules") } | ||
{ test: /\.ts$/, loader: "ts-loader", exclude: path.resolve(__dirname, "node_modules") } | ||
] | ||
}, | ||
externals: { | ||
crypto: "require(\"crypto\");", | ||
}, | ||
node: { | ||
Buffer: false, | ||
crypto: false, | ||
Buffer: false, | ||
crypto: false | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
285501
36
3429
123
4