Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ucan-wg/ts-ucan
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.11.2
Choose a base ref
...
head repository: ucan-wg/ts-ucan
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 7 commits
  • 19 files changed
  • 7 contributors

Commits on Feb 24, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5c0c4dc View commit details
  2. 🚀 Version 0.11.3

    matheus23 committed Feb 24, 2023
    Copy the full SHA
    385cc9a View commit details
  3. 🚀 Version 0.11.4: Now with a README

    😅
    matheus23 committed Feb 24, 2023
    Copy the full SHA
    b9188fc View commit details

Commits on May 31, 2023

  1. Contribution block in README (#101)

    * Added the beginnings of a contrbution block that includes instructions to use yarn, as per #100
    
    * Typos
    
    Signed-off-by: Philipp Krüger <[email protected]>
    
    ---------
    
    Signed-off-by: Philipp Krüger <[email protected]>
    Co-authored-by: Philipp Krüger <[email protected]>
    jeffgca and matheus23 authored May 31, 2023
    Copy the full SHA
    9ae067c View commit details

Commits on Jul 24, 2023

  1. Add UCAN version badge (#103)

    bgins authored Jul 24, 2023
    Copy the full SHA
    d2b87d2 View commit details

Commits on Jul 25, 2023

  1. Update SemVer handling & add 0.9.0 compat (#104)

    * fix semver parsing and update compat to 0.9.1 (for compat with rs-ucan 0.2.0, which uses "0.9.0-canary")
    
    * fix: linting error
    
    ---------
    
    Co-authored-by: Steven Vandevelde <[email protected]>
    blaine and icidasset authored Jul 25, 2023
    Copy the full SHA
    4b1be87 View commit details

Commits on Mar 15, 2024

  1. Add an exportable option to default-plugins (#109)

    Addresses issue #108
    
    ---
    
    * Add an `exportable` option to EcdsaKeyPair
    Addresses issue #108
    
    * Implement and test import / export for ECDSA Keypair
    
    * Import and Export for RSA keys
    
    * Import / Export for EdKeypair
    
    * [WIP] Remove unit8array as a dependency
    
    * [bug] remove unused dependency
    
    * [fix] Normalize back to uintarrays
    
    [Fix] Normalize on uint8arrays
    
    * [feat] Normalize `export` to use JWK types
    
    * [fix] added notes about ed25519 export
    
    * [fix] Revised comment about export to include other parameters
    
    * [fix] Generify ExportableKey type to allow for PrivateKeyJwk return
    types
    kshinn authored Mar 15, 2024
    Copy the full SHA
    bf35b41 View commit details
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -2,3 +2,4 @@ node_modules
.cache
dist
.tool-versions
/packages/ucans/README.md # copied from root
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### v0.11.4

- Upload `README.md` to `@ucans/ucans` on npm.

### v0.11.2

- Add `.js` suffixes to imports for ESM builds
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# ts-ucan
![UCAN](https://img.shields.io/badge/UCAN-v0.8.1-blue)
[![NPM](https://img.shields.io/npm/v/ucans)](https://www.npmjs.com/package/ucans)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/fission-suite/blob/master/LICENSE)
[![Discussions](https://img.shields.io/github/discussions/ucan-wg/ts-ucan)](https://github.com/ucan-wg/ts-ucan/discussions)
@@ -98,18 +99,18 @@ type BuildParams = {
### NPM:
```
npm install --save ucans
npm install --save @ucans/ucans
```
### yarn:
```
yarn add ucans
yarn add @ucans/ucans
```
## Example
```ts
import * as ucans from "ucans"
import * as ucans from "@ucans/ucans"

// in-memory keypair
const keypair = await ucans.EdKeypair.create()
@@ -147,7 +148,7 @@ Using a UCAN to authorize an action is called "invocation".
To verify invocations, you need to use the `verify` function.

```ts
import * as ucans from "ucans"
import * as ucans from "@ucans/ucans"

const serviceDID = "did:key:zabcde..."

@@ -196,7 +197,7 @@ in combination may result in a delegation being possible. Please talk to us
with your use-case and ideas for how a good API for that may work.)
```ts
import * as ucans from "ucans"
import * as ucans from "@ucans/ucans"

// Delegation semantics for path-like capabilities (e.g. "path:/home/abc/")
const PATH_SEMANTICS = {
@@ -226,6 +227,16 @@ const PATH_SEMANTICS = {
}
```
## Contributing
To get started working with this repository:
- `git clone git@github.com:ucan-wg/ts-ucan.git`
- `cd ts-ucan`
- `yarn`
Note that using npm with this repository will likely fail, please use yarn instead.
## Sponsors
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ucans/core",
"version": "0.11.2",
"version": "0.11.4",
"description": "Core UCAN implementation",
"author": "Daniel Holmgren <daniel@fission.codes>",
"repository": {
2 changes: 1 addition & 1 deletion packages/core/src/compatibility.ts
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ export function handleCompatibility(header: unknown, payload: unknown): UcanPart

// parse either the "ucv" or "uav" as a version in the header
// we translate 'uav: 1.0.0' into 'ucv: 0.3.0'
let version: "0.8.1" | "0.3.0" = "0.8.1"
let version: "0.9.1" | "0.3.0" = "0.9.1"
if (!util.hasProp(header, "ucv") || typeof header.ucv !== "string") {
if (!util.hasProp(header, "uav") || typeof header.uav !== "string") {
throw fail("header", "Invalid format: Missing version indicator")
19 changes: 14 additions & 5 deletions packages/core/src/semver.ts
Original file line number Diff line number Diff line change
@@ -28,18 +28,27 @@ const matchesRegex = (regex: RegExp) => (str: string) => {
}

export function parse(version: string): SemVer | null {
const parts = version.split(".")

const sv = version.match(
/^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
)

if (sv === null) {
return null
}

const parts =
`${sv.groups?.major}.${sv.groups?.minor}.${sv.groups?.patch}`.split(".")

if (parts.length !== 3) {
return null
}

if (!parts.every(matchesRegex(NUM_REGEX))) {
return null
}
const [ major, minor, patch ] = parts.map(part => parseInt(part, 10))

const [major, minor, patch] = parts.map(part => parseInt(part, 10))

if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) {
return null
}
16 changes: 8 additions & 8 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -59,16 +59,16 @@ export interface Didable {
did: () => string
}

export interface ExportableKey {
export: (format?: Encodings) => Promise<string>
export interface ExportableKey<T> {
export: () => Promise<T>
}

export interface Keypair {
jwtAlg: string
sign: (msg: Uint8Array) => Promise<Uint8Array>
}

export interface DidableKey extends Didable, Keypair {}
export interface DidableKey extends Didable, Keypair { }

// MISC

@@ -80,21 +80,21 @@ export type Encodings = SupportedEncodings


export interface IndexByAudience {
[ audienceDID: string ]: Array<{
[audienceDID: string]: Array<{
processedUcan: Ucan
capabilities: DelegationChain[]
}>
}

export interface StoreI {
add(ucan: Ucan): Promise<void>
getByAudience(audience: string): Ucan[]
findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null
add(ucan: Ucan): Promise<void>
getByAudience(audience: string): Ucan[]
findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null
findWithCapability(
audience: string,
requiredCapability: Capability,
requiredIssuer: string,
): Iterable<DelegationChain>
): Iterable<DelegationChain>
}

// BUILDER
2 changes: 1 addition & 1 deletion packages/default-plugins/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ucans/default-plugins",
"version": "0.11.2",
"version": "0.11.4",
"description": "Default UCAN plugin set",
"author": "Daniel Holmgren <daniel@fission.codes>",
"repository": {
38 changes: 35 additions & 3 deletions packages/default-plugins/src/ed25519/keypair.ts
Original file line number Diff line number Diff line change
@@ -3,9 +3,10 @@ import * as ed25519 from "@stablelib/ed25519"
import * as crypto from "./crypto.js"

import { DidableKey, Encodings, ExportableKey } from "@ucans/core"
import { PrivateKeyJwk } from "../types.js"


export class EdKeypair implements DidableKey, ExportableKey {
export class EdKeypair implements DidableKey, ExportableKey<PrivateKeyJwk> {

public jwtAlg = "EdDSA"

@@ -45,13 +46,44 @@ export class EdKeypair implements DidableKey, ExportableKey {
return ed25519.sign(this.secretKey, msg)
}

async export(format: Encodings = "base64pad"): Promise<string> {
async export(): Promise<PrivateKeyJwk> {
if (!this.exportable) {
throw new Error("Key is not exportable")
}
return uint8arrays.toString(this.secretKey, format)

/*
* EdDSA is relatively new and not supported everywhere. There's no good documentation
* within the JWK spec or parameter export to be able to reconstruct the key via parameters
* Example, there's no good documentation on parameterizing like other curves: (x, y, n, e)
*
* In an effort to remain compatible with other tooling in the space, the following article
* describes a way of encoding JWK that is at least consistent with other tooling. As our current
* libraries are only able to reconstruct a key via importing a secret key, encoding the secret
* as the `d` parameter seems to make sense and have some compatibility with other tools.
*
* [Link](https://gist.github.com/kousu/f3174af57e1fc42a0a88586b5a5ffdc9)
*
* While `kty` and `crv` are not absolutely required for this to work within the library,
* including them is an attempt to be closer to the [JWK Spec](https://datatracker.ietf.org/doc/html/rfc7517)
* since we are hand rolling this export.
*/
const jwk: PrivateKeyJwk = {
kty: "EC",
crv: "Ed25519",
d: uint8arrays.toString(this.secretKey, "base64pad"),
}
return jwk
}

static async import(jwk: PrivateKeyJwk, params?: { exportable: boolean }): Promise<EdKeypair> {
const { exportable = false } = params || {}

if (jwk.kty !== "EC" || jwk.crv !== "Ed25519") {
throw new Error("Cannot import key of type: ${jwk.kty} curve: ${jwk.crv} into ED25519 key")
}

return EdKeypair.fromSecretKey(jwk.d, { exportable })
}
}


24 changes: 16 additions & 8 deletions packages/default-plugins/src/p256/crypto.ts
Original file line number Diff line number Diff line change
@@ -9,14 +9,16 @@ export const ALG = "ECDSA"
export const DEFAULT_CURVE = "P-256"
export const DEFAULT_HASH_ALG = "SHA-256"

export const generateKeypair = async (): Promise<AvailableCryptoKeyPair> => {
export const generateKeypair = async (
exportable = false
): Promise<AvailableCryptoKeyPair> => {
return await webcrypto.subtle.generateKey(
{
name: ALG,
namedCurve: DEFAULT_CURVE,
},
false,
[ "sign", "verify" ]
exportable,
["sign", "verify"]
)
}

@@ -32,10 +34,10 @@ export const importKeypairJwk = async (
namedCurve: DEFAULT_CURVE,
},
exportable,
["sign" ]
["sign"]
)
const { kty, crv, x, y} = privKeyJwk
const pubKeyJwk = { kty, crv, x, y}
const { kty, crv, x, y } = privKeyJwk
const pubKeyJwk = { kty, crv, x, y }
const publicKey = await webcrypto.subtle.importKey(
"jwk",
pubKeyJwk,
@@ -44,11 +46,17 @@ export const importKeypairJwk = async (
namedCurve: DEFAULT_CURVE,
},
true,
[ "verify" ]
["verify"]
)
return { privateKey, publicKey }
}

export const exportPrivateKeyJwk = async (
keyPair: AvailableCryptoKeyPair
): Promise<PrivateKeyJwk> => {
return await webcrypto.subtle.exportKey("jwk", keyPair.privateKey) as PrivateKeyJwk
}

export const exportKey = async (key: CryptoKey): Promise<Uint8Array> => {
const buf = await webcrypto.subtle.exportKey("raw", key)
return new Uint8Array(buf)
@@ -62,7 +70,7 @@ export const importKey = async (
key,
{ name: ALG, namedCurve: DEFAULT_CURVE },
true,
[ "verify" ]
["verify"]
)
}

37 changes: 21 additions & 16 deletions packages/default-plugins/src/p256/keypair.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { webcrypto } from "one-webcrypto"
import * as uint8arrays from "uint8arrays"
import { DidableKey, Encodings, ExportableKey } from "@ucans/core"
import { DidableKey, ExportableKey } from "@ucans/core"

import * as crypto from "./crypto.js"
import {
@@ -10,7 +8,7 @@ import {
} from "../types.js"


export class EcdsaKeypair implements DidableKey, ExportableKey {
export class EcdsaKeypair implements DidableKey, ExportableKey<PrivateKeyJwk> {

public jwtAlg = "ES256"

@@ -32,7 +30,7 @@ export class EcdsaKeypair implements DidableKey, ExportableKey {
exportable?: boolean
}): Promise<EcdsaKeypair> {
const { exportable = false } = params || {}
const keypair = await crypto.generateKeypair()
const keypair = await crypto.generateKeypair(exportable)

if (!isAvailableCryptoKeyPair(keypair)) {
throw new Error(`Couldn't generate valid keypair`)
@@ -47,12 +45,12 @@ export class EcdsaKeypair implements DidableKey, ExportableKey {
params?: {
exportable?: boolean
}): Promise<EcdsaKeypair> {
const { exportable = false } = params || {}
const keypair = await crypto.importKeypairJwk(jwk, exportable)
const { exportable = false } = params || {}
const keypair = await crypto.importKeypairJwk(jwk, exportable)

if (!isAvailableCryptoKeyPair(keypair)) {
throw new Error(`Couldn't generate valid keypair`)
}
if (!isAvailableCryptoKeyPair(keypair)) {
throw new Error(`Couldn't generate valid keypair`)
}

const publicKey = await crypto.exportKey(keypair.publicKey)
return new EcdsaKeypair(keypair, publicKey, exportable)
@@ -66,15 +64,22 @@ export class EcdsaKeypair implements DidableKey, ExportableKey {
return await crypto.sign(msg, this.keypair.privateKey)
}

async export(format: Encodings = "base64pad"): Promise<string> {
async export(): Promise<PrivateKeyJwk> {
if (!this.exportable) {
throw new Error("Key is not exportable")
}
const arrayBuffer = await webcrypto.subtle.exportKey(
"pkcs8",
this.keypair.privateKey
)
return uint8arrays.toString(new Uint8Array(arrayBuffer), format)
return await crypto.exportPrivateKeyJwk(this.keypair)
}

/**
* Convenience function on the Keypair class to allow for keys to be exported / persisted.
* This is most useful for situations where you want to have consistent keys between restarts.
* A Developer can export a key, save it in a vault, and rehydrate it for use in a later run.
* @param jwk
* @returns
*/
static async import(jwk: PrivateKeyJwk): Promise<EcdsaKeypair> {
return EcdsaKeypair.importFromJwk(jwk, { exportable: true })
}
}

Loading