-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
update signature bundle format #46
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -75,20 +75,34 @@ https://rekor.sigstore.dev/api/v1/log/entries/43553c769cd0bd99aee4350d2e78ca3fb0 | |||||||||||||||||
|
||||||||||||||||||
You should see that a browser window is opened to the Sigstore OAuth page. | ||||||||||||||||||
After authenticating with one of the available idenity providers, a signature | ||||||||||||||||||
will be generated and written to the file named "signature". | ||||||||||||||||||
bundle will be generated and written to the file named "signature". | ||||||||||||||||||
|
||||||||||||||||||
``` | ||||||||||||||||||
$ cat signature | ||||||||||||||||||
$ cat signature | jq | ||||||||||||||||||
|
||||||||||||||||||
MEUCIQC7Rrrjmrwdxuc2qvWiWzaoUdV8+VFv+fvDquvAGmxr3AIgaPEqQ5YvxjfeqgXYXvISzgyVA8y/Zw+G/LDYlt2RHMk= | ||||||||||||||||||
{ | ||||||||||||||||||
"attestationType": "attestation/blob", | ||||||||||||||||||
"attestation": { | ||||||||||||||||||
"payloadHash": "3ad055f2b0d850290fbe0ce63f8c60adf492901c5950ac01e0893ebb47bea4d8", | ||||||||||||||||||
"payloadAlgorithm": "sha256", | ||||||||||||||||||
"base64Signature": "MEUCIQC7Rrrjmrwdxuc2qvWiWzaoUdV8+VFv+fvDquvAGmxr3AIgaPEqQ5YvxjfeqgXYXvISzgyVA8y/Zw+G/LDYlt2RHMk=" | ||||||||||||||||||
}, | ||||||||||||||||||
"cert": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNvakNDQWllZ0F3SUJBZ0lVQkswdTBRdWF3dkVJWm82YS9keGVzbEthZU9jd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJd09ERTJNVFEwTVRJd1doY05Nakl3T0RFMk1UUTFNVEl3V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVFRWlrR2hsMXdzSzFLMStSYTBQRU9SQzh5SXE4bTBWM0VXSSsKbDIrY2w3aUFyM0xrc292Nk5qUmtyc3VjUVVpMmRmSi9uUlZlYi9rREw1WTJYbS9VTGFPQ0FVWXdnZ0ZDTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVnNUtlCk9JWnRvRktnd3ZKVjZSSHB6M2hWcTFjd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0h3WURWUjBSQVFIL0JCVXdFNEVSWW5KcFlXNUFaR1ZvWVcxbGNpNWpiMjB3TEFZS0t3WUJCQUdEdnpBQgpBUVFlYUhSMGNITTZMeTluYVhSb2RXSXVZMjl0TDJ4dloybHVMMjloZFhSb01JR0xCZ29yQmdFRUFkWjVBZ1FDCkJIMEVld0I1QUhjQUNHQ1M4Q2hTLzJoRjBkRnJKNFNjUldjWXJCWTl3empTYmVhOElnWTJiM0lBQUFHQ3B4b1YKNVFBQUJBTUFTREJHQWlFQWtSZ1kxczlQajNFbjI1d3o0aHpXbEQzeStBYXVBSmRXd2tvWFdUZmlYd2NDSVFEMgo2a1JBU1BPbzBhdDIzNy9zTVVrd1YvSEhEQ0lBNkVnZzl2eG1pUEJqbURBS0JnZ3Foa2pPUFFRREF3TnBBREJtCkFqRUF6eDBYU3hMSkdKT29OWjN6Zk02RkUxbUZZZ3daQUJIRTFBb2xFd3ZYdTdZNUdFMkU2RWxzVjRHVDR2YlkKd2FOVUFqRUFqZzhaZlA3WXR0L1RIOVBXV1hqYkhpR3laamlpMHFpVVpTZGthTVJseGx0aC9QNXpGaHdkRTN2cQpoL1Z0UUNCdwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDR2pDQ0FhR2dBd0lCQWdJVUFMblZpVmZuVTBickphc21Sa0hybi9VbmZhUXdDZ1lJS29aSXpqMEVBd013CktqRVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVJFd0R3WURWUVFERXdoemFXZHpkRzl5WlRBZUZ3MHkKTWpBME1UTXlNREEyTVRWYUZ3MHpNVEV3TURVeE16VTJOVGhhTURjeEZUQVRCZ05WQkFvVERITnBaM04wYjNKbApMbVJsZGpFZU1Cd0dBMVVFQXhNVmMybG5jM1J2Y21VdGFXNTBaWEp0WldScFlYUmxNSFl3RUFZSEtvWkl6ajBDCkFRWUZLNEVFQUNJRFlnQUU4UlZTL3lzSCtOT3Z1RFp5UEladGlsZ1VGOU5sYXJZcEFkOUhQMXZCQkgxVTVDVjcKN0xTUzdzMFppSDRuRTdIdjdwdFM2THZ2Ui9TVGs3OThMVmdNekxsSjRIZUlmRjN0SFNhZXhMY1lwU0FTcjFrUwowTi9SZ0JKei85aldDaVhubzNzd2VUQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0V3WURWUjBsQkF3d0NnWUlLd1lCCkJRVUhBd013RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVTM5UHB6MVlrRVpiNXFOanAKS0ZXaXhpNFlaRDh3SHdZRFZSMGpCQmd3Rm9BVVdNQWVYNUZGcFdhcGVzeVFvWk1pMENyRnhmb3dDZ1lJS29aSQp6ajBFQXdNRFp3QXdaQUl3UENzUUs0RFlpWllEUElhRGk1SEZLbmZ4WHg2QVNTVm1FUmZzeW5ZQmlYMlg2U0pSCm5aVTg0LzlEWmRuRnZ2eG1BakJPdDZRcEJsYzRKLzBEeHZrVENxcGNsdnppTDZCQ0NQbmpkbElCM1B1M0J4c1AKbXlnVVk3SWkyemJkQ2RsaWlvdz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQjl6Q0NBWHlnQXdJQkFnSVVBTFpOQVBGZHhIUHdqZURsb0R3eVlDaEFPLzR3Q2dZSUtvWkl6ajBFQXdNdwpLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQWVGdzB5Ck1URXdNRGN4TXpVMk5UbGFGdzB6TVRFd01EVXhNelUyTlRoYU1Db3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmwKTG1SbGRqRVJNQThHQTFVRUF4TUljMmxuYzNSdmNtVXdkakFRQmdjcWhrak9QUUlCQmdVcmdRUUFJZ05pQUFUNwpYZUZUNHJiM1BRR3dTNElhanRMazMvT2xucGdhbmdhQmNsWXBzWUJyNWkrNHluQjA3Y2ViM0xQME9JT1pkeGV4Clg2OWM1aVZ1eUpSUStIejA1eWkrVUYzdUJXQWxIcGlTNXNoMCtIMkdIRTdTWHJrMUVDNW0xVHIxOUw5Z2c5MmoKWXpCaE1BNEdBMVVkRHdFQi93UUVBd0lCQmpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJSWQp3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFmQmdOVkhTTUVHREFXZ0JSWXdCNWZrVVdsWnFsNnpKQ2hreUxRCktzWEYrakFLQmdncWhrak9QUVFEQXdOcEFEQm1BakVBajFuSGVYWnArMTNOV0JOYStFRHNEUDhHMVdXZzF0Q00KV1AvV0hQcXBhVm8wamhzd2VORlpnU3MwZUU3d1lJNHFBakVBMldCOW90OThzSWtvRjN2WllkZDMvVnRXQjViOQpUTk1lYTdJeC9zdEo1VGZjTExlQUJMRTRCTkpPc1E0dm5CSEoKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==", | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
"signedEntryTimestamp": "MEUCIQDLVDIXVubRBoS6tXXWptNEQNUuwPpnflrd93uBCsm3QAIgPZA5pyWhPpv9hftq0dk8b7ipjNCBzM5yIaSVXyokXbU=", | ||||||||||||||||||
"integratedTime": 1655485921, | ||||||||||||||||||
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", | ||||||||||||||||||
"logIndex": 2698234 | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
The Fulcio signing certificate will be written to a file named | ||||||||||||||||||
`signingcert.pem`. You can inspect the contents of the signing certificate with | ||||||||||||||||||
the following: | ||||||||||||||||||
You'll see that the signature bundle contains the SHA256 digest of the | ||||||||||||||||||
artifact, the signature, the signing certificate and metadata about the | ||||||||||||||||||
entry which was made in Rekor. | ||||||||||||||||||
|
||||||||||||||||||
You can extract and view the signing certificate with the following: | ||||||||||||||||||
``` | ||||||||||||||||||
$ openssl x509 -in signingcert.pem -text | ||||||||||||||||||
$ cat signature | jq --raw-output '.cert' | base64 -d | openssl x509 -text | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
Certificate: | ||||||||||||||||||
Data: | ||||||||||||||||||
Version: 3 (0x2) | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -27,26 +27,46 @@ export interface Envelope { | |||||
signatures: Signature[]; | ||||||
} | ||||||
|
||||||
export interface DSSEBundle { | ||||||
attestationType: string; | ||||||
attestation: Envelope; | ||||||
cert: string; | ||||||
signedEntryTimestamp: string; | ||||||
integratedTime: number; | ||||||
logIndex: number; | ||||||
logID: string; | ||||||
} | ||||||
|
||||||
export async function sign( | ||||||
payload: Buffer, | ||||||
payloadType: string, | ||||||
options: sigstore.SignOptions = {} | ||||||
): Promise<Envelope> { | ||||||
): Promise<DSSEBundle> { | ||||||
const paeBuffer = pae(payloadType, payload); | ||||||
const signedPayload = await sigstore.sign(paeBuffer, options); | ||||||
const bundle = await sigstore.sign(paeBuffer, options); | ||||||
|
||||||
const envelope: Envelope = { | ||||||
payloadType: payloadType, | ||||||
payload: payload.toString('base64'), | ||||||
signatures: [ | ||||||
{ | ||||||
keyid: '', | ||||||
sig: signedPayload.base64Signature, | ||||||
sig: bundle.attestation.base64Signature, | ||||||
}, | ||||||
], | ||||||
}; | ||||||
|
||||||
return envelope; | ||||||
const dsseBundle: DSSEBundle = { | ||||||
attestationType: 'attestation/dsse', | ||||||
attestation: envelope, | ||||||
cert: bundle.cert, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
signedEntryTimestamp: bundle.signedEntryTimestamp, | ||||||
integratedTime: bundle.integratedTime, | ||||||
logIndex: bundle.logIndex, | ||||||
logID: bundle.logID, | ||||||
}; | ||||||
|
||||||
return dsseBundle; | ||||||
} | ||||||
|
||||||
export async function verify( | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,30 +13,22 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||||||
See the License for the specific language governing permissions and | ||||||||||
limitations under the License. | ||||||||||
*/ | ||||||||||
import { Entry, Fulcio, Rekor } from './client'; | ||||||||||
import { Fulcio, Rekor } from './client'; | ||||||||||
import { generateKeyPair, hash, signBlob } from './crypto'; | ||||||||||
import { Provider } from './identity'; | ||||||||||
import { base64Decode, base64Encode, extractJWTSubject } from './util'; | ||||||||||
import { base64Encode, extractJWTSubject } from './util'; | ||||||||||
|
||||||||||
export interface SignOptions { | ||||||||||
fulcio: Fulcio; | ||||||||||
rekor: Rekor; | ||||||||||
identityProviders: Provider[]; | ||||||||||
} | ||||||||||
|
||||||||||
export interface SignedPayload { | ||||||||||
base64Signature: string; | ||||||||||
export interface SigstoreBundle { | ||||||||||
attestationType: string; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. enum? |
||||||||||
attestation: Record<string, string>; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be updated to a new interface? |
||||||||||
cert: string; | ||||||||||
bundle?: RekorBundle; | ||||||||||
} | ||||||||||
|
||||||||||
export interface RekorBundle { | ||||||||||
signedEntryTimestamp: string; | ||||||||||
payload: RekorPayload; | ||||||||||
} | ||||||||||
|
||||||||||
export interface RekorPayload { | ||||||||||
body: object; | ||||||||||
integratedTime: number; | ||||||||||
logIndex: number; | ||||||||||
logID: string; | ||||||||||
|
@@ -54,7 +46,7 @@ export class Signer { | |||||||||
this.identityProviders = options.identityProviders; | ||||||||||
} | ||||||||||
|
||||||||||
public async sign(payload: Buffer): Promise<SignedPayload> { | ||||||||||
public async sign(payload: Buffer): Promise<SigstoreBundle> { | ||||||||||
// Create emphemeral key pair | ||||||||||
const keypair = generateKeyPair(); | ||||||||||
|
||||||||||
|
@@ -98,12 +90,21 @@ export class Signer { | |||||||||
`https://rekor.sigstore.dev/api/v1/log/entries/${entry.uuid}` | ||||||||||
); | ||||||||||
|
||||||||||
const signedPayload: SignedPayload = { | ||||||||||
base64Signature: signature, | ||||||||||
const bundle: SigstoreBundle = { | ||||||||||
attestationType: 'attestation/blob', | ||||||||||
attestation: { | ||||||||||
payloadHash: digest, | ||||||||||
payloadAlgorithm: 'sha256', | ||||||||||
base64Signature: signature, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the current proposal
Suggested change
|
||||||||||
}, | ||||||||||
cert: b64Certificate, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to align with the current (in flight spec)?
Suggested change
|
||||||||||
bundle: entryToBundle(entry), | ||||||||||
signedEntryTimestamp: entry.verification.signedEntryTimestamp, | ||||||||||
integratedTime: entry.integratedTime, | ||||||||||
logID: entry.logID, | ||||||||||
logIndex: entry.logIndex, | ||||||||||
}; | ||||||||||
return signedPayload; | ||||||||||
|
||||||||||
return bundle; | ||||||||||
} | ||||||||||
|
||||||||||
private async getIdentityToken(): Promise<string> { | ||||||||||
|
@@ -123,19 +124,3 @@ export class Signer { | |||||||||
throw new Error(`Identity token providers failed: ${aggErrs}`); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
function entryToBundle(entry: Entry): RekorBundle | undefined { | ||||||||||
if (!entry.verification) { | ||||||||||
return; | ||||||||||
} | ||||||||||
|
||||||||||
return { | ||||||||||
signedEntryTimestamp: entry.verification.signedEntryTimestamp, | ||||||||||
payload: { | ||||||||||
body: JSON.parse(base64Decode(entry.body)), | ||||||||||
integratedTime: entry.integratedTime, | ||||||||||
logIndex: entry.logIndex, | ||||||||||
logID: entry.logID, | ||||||||||
}, | ||||||||||
}; | ||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! 🙌