Skip to content

Commit

Permalink
Kms keypair (bluesky-social#270)
Browse files Browse the repository at this point in the history
* scaffold package

* kms working!

* interface

* normlize for low s

* no der encoding in crypto

* comments

* rm kms tests

* pr feedback
  • Loading branch information
dholms authored Oct 27, 2022
1 parent e942864 commit 9b66f20
Show file tree
Hide file tree
Showing 8 changed files with 875 additions and 4 deletions.
3 changes: 3 additions & 0 deletions packages/aws-kms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# AWS KMS

A DidableKeypair-compatible wrapper for AWS KMS.
23 changes: 23 additions & 0 deletions packages/aws-kms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@atproto/aws-kms",
"version": "0.0.1",
"main": "src/index.ts",
"license": "MIT",
"scripts": {
"start": "ts-node src/run.ts",
"prettier": "prettier --check src/",
"prettier:fix": "prettier --write src/",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "yarn lint --fix",
"verify": "run-p prettier lint",
"verify:fix": "yarn prettier:fix && yarn lint:fix",
"build": "esbuild src/index.ts --define:process.env.NODE_ENV=\\\"production\\\" --bundle --platform=node --sourcemap --outfile=dist/index.js",
"postbuild": "tsc --build tsconfig.build.json"
},
"dependencies": {
"@atproto/crypto": "*",
"@aws-sdk/client-kms": "^3.196.0",
"@noble/secp256k1": "^1.7.0",
"key-encoder": "^2.0.3"
}
}
66 changes: 66 additions & 0 deletions packages/aws-kms/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as aws from '@aws-sdk/client-kms'
import * as secp from '@noble/secp256k1'
import * as crypto from '@atproto/crypto'
import KeyEncoder from 'key-encoder'

const keyEncoder = new KeyEncoder('secp256k1')

export type KmsConfig = { keyId: string } & Omit<
aws.KMSClientConfig,
'apiVersion'
>

export class KmsKeypair implements crypto.DidableKey {
jwtAlg = crypto.SECP256K1_JWT_ALG

constructor(
private client: aws.KMS,
private keyId: string,
private publicKey: Uint8Array,
) {}

static async load(cfg: KmsConfig) {
const { keyId, ...rest } = cfg
const client = new aws.KMS({
...rest,
apiVersion: '2014-11-01',
})
const res = await client.getPublicKey({ KeyId: keyId })
if (!res.PublicKey) {
throw new Error('Could not find public key')
}
// public key comes back DER-encoded, so we translate it to raw 65 byte encoding
const rawPublicKeyHex = keyEncoder.encodePublic(
Buffer.from(res.PublicKey),
'der',
'raw',
)
const publicKey = secp.utils.hexToBytes(rawPublicKeyHex)
return new KmsKeypair(client, keyId, publicKey)
}

did(): string {
return crypto.formatDidKey(this.jwtAlg, this.publicKey)
}

async sign(msg: Uint8Array): Promise<Uint8Array> {
const res = await this.client.sign({
KeyId: this.keyId,
Message: msg,
SigningAlgorithm: 'ECDSA_SHA_256',
})
if (!res.Signature) {
throw new Error('Could not get signature')
}
// signature comes back DER encoded & not-normalized
// we translate to raw 64 byte encoding
// we also normalize s as no more than 1/2 prime order to pass strict verification
// (prevents duplicating a signature)
// more: https://github.com/bitcoin-core/secp256k1/blob/a1102b12196ea27f44d6201de4d25926a2ae9640/include/secp256k1.h#L530-L534
const sig = secp.Signature.fromDER(res.Signature)
const normalized = sig.normalizeS()
return normalized.toCompactRawBytes()
}
}

export default KmsKeypair
4 changes: 4 additions & 0 deletions packages/aws-kms/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["**/*.spec.ts", "**/*.test.ts"]
}
8 changes: 8 additions & 0 deletions packages/aws-kms/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist", // Your outDir,
"emitDeclarationOnly": true
},
"include": ["./src","__tests__/**/**.ts"]
}
3 changes: 2 additions & 1 deletion packages/crypto/src/secp256k1/keypair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export class Secp256k1Keypair implements ucan.DidableKey {

async sign(msg: Uint8Array): Promise<Uint8Array> {
const msgHash = await secp.utils.sha256(msg)
return secp.sign(msgHash, this.privateKey)
// return raw 64 byte sig not DER-encoded
return secp.sign(msgHash, this.privateKey, { der: false })
}

async export(): Promise<Uint8Array> {
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
{ "path": "./packages/pds/tsconfig.build.json" },
{ "path": "./packages/api/tsconfig.build.json" },
{ "path": "./packages/auth/tsconfig.build.json" },
{ "path": "./packages/aws-kms/tsconfig.build.json" },
{ "path": "./packages/common/tsconfig.build.json" },
{ "path": "./packages/crypto/tsconfig.build.json" },
{ "path": "./packages/dev-env" },
Expand Down
Loading

0 comments on commit 9b66f20

Please sign in to comment.