src/crypto/key/adapters/vault-key.service.ts
Properties |
Methods |
|
constructor(httpService: HttpService, configService: ConfigService, cryptoService: CryptoImplementationService, certRepository: Repository<CertEntity>)
|
|||||||||||||||
Parameters :
|
Private headers |
Type : literal type
|
Private vaultUrl |
Type : string
|
Async create | ||||||
create(tenantId: string)
|
||||||
Inherited from
KeyService
|
||||||
Defined in
KeyService:106
|
||||||
Creates a new keypair in the vault
Parameters :
Returns :
unknown
|
Private getJWK |
getJWK(key: string, tenantId: string)
|
Returns :
Promise<JWK>
|
getKeys | ||||||
getKeys(tenantId: string)
|
||||||
Parameters :
Returns :
Promise<KeyObj[]>
|
getKid | ||||||
getKid(tenantId: string)
|
||||||
Inherited from
KeyService
|
||||||
Defined in
KeyService:128
|
||||||
Get all keys and take the first one.
Parameters :
Returns :
Promise<string>
|
Async getPublicKey |
getPublicKey(type, tenantId: string, keyId?: string)
|
Inherited from
KeyService
|
Defined in
KeyService:157
|
Gets the public key and converts it to a KeyLike object.
Returns :
Promise<string>
|
Async getPublicKey |
getPublicKey(type, tenantId: string, keyId: string)
|
Returns :
Promise<JWK>
|
Async getPublicKey |
getPublicKey(type: "jwk" | "pem", tenantId: string, keyId?: string)
|
Returns :
Promise<JWK | string>
|
import | |||||||||
import(tenantId: string, body: KeyImportDto)
|
|||||||||
Inherited from
KeyService
|
|||||||||
Defined in
KeyService:62
|
|||||||||
Parameters :
Returns :
Promise<string>
|
Async init | ||||||
init(tenantId: string)
|
||||||
Inherited from
KeyService
|
||||||
Defined in
KeyService:45
|
||||||
Create a new transit for the tenant.
Parameters :
Returns :
unknown
|
Async sign |
sign(value: string, tenantId: string, keyId?: string)
|
Signs a value with a key in the vault.
Returns :
Promise<string>
|
signer | ||||||
signer(tenantId: string)
|
||||||
Inherited from
KeyService
|
||||||
Defined in
KeyService:95
|
||||||
Get the signer for the key service
Parameters :
Returns :
Promise<Signer>
|
Async signJWT | |||||||||||||||
signJWT(payload: JwtPayload, header: JWTHeaderParameters, tenantId: string, keyId?: string)
|
|||||||||||||||
Inherited from
KeyService
|
|||||||||||||||
Defined in
KeyService:232
|
|||||||||||||||
Creates a proof of possession jwt.
Parameters :
Returns :
Promise<string>
|
Protected getCertificate |
getCertificate(tenantId: string, keyId: string)
|
Inherited from
KeyService
|
Defined in
KeyService:85
|
Get the certificate for the given key id.
Returns :
Promise<string>
|
import { HttpService } from "@nestjs/axios";
import { ConfigService } from "@nestjs/config";
import { JwtPayload, Signer } from "@sd-jwt/types";
import { exportJWK, importSPKI, JWK, JWTHeaderParameters } from "jose";
import { firstValueFrom } from "rxjs";
import { Repository } from "typeorm/repository/Repository";
import { v4 } from "uuid";
import {
CryptoImplementationService,
CryptoType,
} from "../crypto-implementation/crypto-implementation.service";
import { KeyImportDto } from "../dto/key-import.dto";
import { KeyObj } from "../dto/key-object.dto";
import { CertEntity } from "../entities/cert.entity";
import { KeyService } from "../key.service";
export class VaultKeyService extends KeyService {
// url to the vault instance
private vaultUrl: string;
// headers for the vault api
private headers: { headers: { "X-Vault-Token": string } };
constructor(
private httpService: HttpService,
configService: ConfigService,
private cryptoService: CryptoImplementationService,
certRepository: Repository<CertEntity>,
) {
super(configService, certRepository);
this.vaultUrl = this.configService.get<string>("VAULT_URL") as string;
this.headers = {
headers: {
"X-Vault-Token": this.configService.get<string>(
"VAULT_TOKEN",
) as string,
},
};
}
/**
* Create a new transit for the tenant.
* @param tenantId
*/
async init(tenantId: string) {
//TODO: what to do when it throws an error e.g. when the transit already exists
await firstValueFrom(
this.httpService.post(
`${this.vaultUrl}/v1/sys/mounts/${tenantId}`,
{
type: "transit",
},
this.headers,
),
).catch((err) => {
console.error(JSON.stringify(err.response.data, null, 2));
});
return this.create(tenantId);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import(tenantId: string, body: KeyImportDto): Promise<string> {
throw new Error("Importing not supported by VaultKeyService");
}
getKeys(tenantId: string): Promise<KeyObj[]> {
return firstValueFrom(
this.httpService.get<any>(
`${this.vaultUrl}/v1/${tenantId}/keys?list=true`,
this.headers,
),
).then((res) => {
//TODO: get all the public keys from the vault
return Promise.all(
res.data.data.keys.map(async (id: string) => {
const publicKey = await this.getPublicKey(
"jwk",
tenantId,
id,
);
const crt = await this.getCertificate(tenantId, id);
return {
id,
publicKey,
crt,
} as KeyObj;
}),
);
});
}
/**
* Get the signer for the key service
*/
signer(tenantId: string): Promise<Signer> {
//TODO: validate if this is correct.
return Promise.resolve((input: string) => this.sign(input, tenantId));
}
/**
* Creates a new keypair in the vault
* @param createKeyDto
* @param user
* @returns
*/
async create(tenantId: string) {
const types: Map<CryptoType, string> = new Map();
types.set("ES256", "ecdsa-p256");
const id = v4();
await firstValueFrom(
this.httpService.post(
`${this.vaultUrl}/v1/${tenantId}/keys/${id}`,
{
exportable: false,
type: types.get(this.cryptoService.getAlg()),
},
this.headers,
),
);
return id;
}
/**
* Get all keys and take the first one.
* @param tenantId
* @returns
*/
getKid(tenantId: string): Promise<string> {
return firstValueFrom(
this.httpService.get<any>(
`${this.vaultUrl}/v1/${tenantId}/keys?list=true`,
this.headers,
),
).then(
(res) => {
if (
!res.data.data.keys ||
(res.data.data.keys as string[]).length === 0
) {
throw new Error("No keys found");
}
return (res.data.data.keys as string[])[0];
},
(err) => {
throw new Error(
`Error getting keys for tenant ${tenantId}: ${err.message}`,
);
},
);
}
/**
* Gets the public key and converts it to a KeyLike object.
* @param id
* @returns
*/
async getPublicKey(
type: "pem",
tenantId: string,
keyId?: string,
): Promise<string>;
async getPublicKey(
type: "jwk",
tenantId: string,
keyId: string,
): Promise<JWK>;
async getPublicKey(
type: "jwk" | "pem",
tenantId: string,
keyId?: string,
): Promise<JWK | string> {
if (!keyId) {
keyId = await this.getKid(tenantId);
}
return firstValueFrom(
this.httpService.get<any>(
`${this.vaultUrl}/v1/${tenantId}/keys/${keyId}`,
this.headers,
),
).then(async (res) => {
return type === "pem"
? (res.data.data.keys["1"].public_key as string)
: await this.getJWK(
res.data.data.keys["1"].public_key,
tenantId,
);
});
}
private getJWK(key: string, tenantId: string): Promise<JWK> {
return importSPKI(key, this.cryptoService.getAlg())
.then((cryptoKey) => exportJWK(cryptoKey))
.then(async (jwk) => {
jwk.kid = await this.getKid(tenantId);
return jwk;
});
}
/**
* Signs a value with a key in the vault.
* @param id
* @param user
* @param value
* @returns
*/
async sign(
value: string,
tenantId: string,
keyId?: string,
): Promise<string> {
if (!keyId) {
keyId = await this.getKid(tenantId);
}
return firstValueFrom(
this.httpService.post(
`${this.vaultUrl}/v1/${tenantId}/sign/${keyId}`,
{
input: Buffer.from(value).toString("base64"),
marshaling_algorithm: "jws",
},
this.headers,
),
).then((res) => res.data.data.signature.split(":")[2]);
}
/**
* Creates a proof of possession jwt.
* @param user
* @param value
*/
async signJWT(
payload: JwtPayload,
header: JWTHeaderParameters,
tenantId: string,
keyId?: string,
): Promise<string> {
// Convert header and payload to Base64 to prepare for Vault
const encodedHeader = Buffer.from(JSON.stringify(header)).toString(
"base64url",
);
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
"base64url",
);
const signingInput = `${encodedHeader}.${encodedPayload}`;
// Request to Vault for signing
try {
const signature = await this.sign(signingInput, tenantId, keyId);
return `${encodedHeader}.${encodedPayload}.${signature}`;
} catch (error) {
console.error("Error signing JWT with Vault:", error);
throw error;
}
}
}