Skip to content

Commit

Permalink
Add sign() builtin for emitting digital signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
mcy committed Jul 2, 2021
1 parent ac8a48f commit 40301a7
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 28 deletions.
96 changes: 96 additions & 0 deletions cmd/ascii2der/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
package main

import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"reflect"
)

// NOTE: If adding a builtin, remember to document it in language.txt!
Expand Down Expand Up @@ -55,6 +61,96 @@ var builtins = map[string]func(*scanner, [][]byte) ([]byte, error){
return nil, errors.New("expected one or two arguments to var()")
}
},

// sign(algorithm, key, message) expands into a digital signature for message
// using the given algorithm and key. key must be a private key in PKCS #8
// format.
//
// The supported algorithm strings are:
// - "RSA_PKCS1_SHA1", RSA_PKCS1_SHA256", "RSA_PKCS1_SHA384",
// "RSA_PKCS1_SHA512", for RSA-SSA with the specified hash function.
// - "ECDSA_SHA256", "ECDSA_SHA384", "ECDSA_SHA512", for ECDSA with the
// specified hash function.
// - "Ed25519" for itself.
"sign": func(scanner *scanner, args [][]byte) ([]byte, error) {
if len(args) != 3 {
return nil, errors.New("expected two arguments to sign()")
}

pk8, err := x509.ParsePKCS8PrivateKey(args[1])
if err != nil {
return nil, err
}

var signer crypto.Signer
var hash crypto.Hash
switch string(args[0]) {
case "RSA_PKCS1_SHA1":
key, ok := pk8.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA1
case "RSA_PKCS1_SHA256":
key, ok := pk8.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA256
case "RSA_PKCS1_SHA384":
key, ok := pk8.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA384
case "RSA_PKCS1_SHA512":
key, ok := pk8.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA512
case "ECSDA_SHA256":
key, ok := pk8.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected ECDSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA256
case "ECSDA_SHA384":
key, ok := pk8.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected ECDSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA384
case "ECSDA_SHA512":
key, ok := pk8.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected ECDSA key, got %v", reflect.TypeOf(key))
}
signer = key
hash = crypto.SHA512
case "Ed22519":
key, ok := pk8.(ed25519.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected Ed25519 key, got %v", reflect.TypeOf(key))
}
signer = key
}

digest := args[2]
if hash > 0 {
hash := hash.New()
hash.Write(digest)
digest = hash.Sum(nil)
}

return signer.Sign(nil, digest, hash)
},
}

func executeBuiltin(scanner *scanner, name string, args [][]byte) ([]byte, error) {
Expand Down
4 changes: 4 additions & 0 deletions language.txt
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ define(`ffff`, "payload")
var("payload")
var(var(`ffff`)) # Same as above, since var(`ffff`) expands to "payload".

# sign(algo, key, message) expands to a digital signature for message,
# using the given algorithm string (e.g. "ECDSA_SHA256") and private key. The
# supported key formats and algorithms can be found in
# cmd/ascii2der/builtins.go.

# Disassembler.

Expand Down
147 changes: 147 additions & 0 deletions samples/cert_with_sign.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# This is the same certificate as cert.txt, but with the signature generated
# using sign().
#
# ascii2der will assemble both files to equal byte strings, because RSA-SSA
# happens to be deterministic.

# Our private key, in PKCS #8 form.
define("my_key", SEQUENCE {
INTEGER { 0 }
SEQUENCE {
# rsaEncryption
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.1 }
NULL {}
}
OCTET_STRING {
SEQUENCE {
INTEGER { 0 }
INTEGER { `00d82bc8a632e462ff4df3d0ad598b45a7bdf147bf09587b22bd35ae97258694a080c0b41f7691674631d01084b7221e70239172c8e96d793a8577800fc4951675c54a714cc8633fa3f2639c2a4f9afacbc1716e288528a0271e651cae07d55b6f2d43ed2b90b18caf246daee9173a05c1bfb81cae653b1b58c2d9aed6aa6788f1` }
INTEGER { 65537 }
INTEGER { `008072d3d15de033aafc88e9f0778ab8230a4c7a935b5c461ec84b43a8f0555daf599227f5a220983b2f92309e8bab2c66f9db8d5730cd2a01ca18cdf1909ffe2d791c40960c5161ea0b743f9cf43270a1c33666bdab55fc55c24f48fe5a618d07f8247a12a31972cb7234dbe9d45854948266156e38b2dc6d6215d74820809401` }
INTEGER { `00f12f2c19ee1ecf2c999b87bdafde60eace3790faad8f9adec13b14c6dfb69f8795a1d0fe65494250b59534014b918453042012952ae6f5786342999600725491` }
INTEGER { `00e57341d15469ec0bb5d389a0f0ada58a18d73776d9e69ef134049a918e475d4bea46f12d0b2468c972fc33a739a6bcdada8019376a0c466048d98278a2a49e61` }
INTEGER { `0be99d8f0650e540b9b191e9cf96f74881b902e32ed169ffd8a1776c3f3e80f0ac765aa14615713e1549f250a20fe4ee48c4e0c6176162fc7842a0dd64d640d1` }
INTEGER { `00e4d74e168bdd5499dd4fcc5d228ddda35ce111254d7010a7ba5cb91860d1d64007b99782783168fd39dc455c0c48bae47fb5f0f06ea92d6b8c5cbb1ebbfff921` }
INTEGER { `00bef4572c74da6ba545cd36a288ef12685b07577950c973ad32b0690798dd9a86568231ef0765bd0a49fbb03aac3c1f94dadc97d23a03750132ba230408363ca1` }
}
}
})

# The "to be signed" portion of our cert.
define("tbs_cert", SEQUENCE {
[0] {
INTEGER { 2 }
}
INTEGER { `00fbb04c2eab109b0c` }
SEQUENCE {
# sha1WithRSAEncryption
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.5 }
NULL {}
}
SEQUENCE {
SET {
SEQUENCE {
# countryName
OBJECT_IDENTIFIER { 2.5.4.6 }
PrintableString { "AU" }
}
}
SET {
SEQUENCE {
# stateOrProvinceName
OBJECT_IDENTIFIER { 2.5.4.8 }
UTF8String { "Some-State" }
}
}
SET {
SEQUENCE {
# organizationName
OBJECT_IDENTIFIER { 2.5.4.10 }
UTF8String { "Internet Widgits Pty Ltd" }
}
}
}
SEQUENCE {
UTCTime { "140423205040Z" }
UTCTime { "170422205040Z" }
}
SEQUENCE {
SET {
SEQUENCE {
# countryName
OBJECT_IDENTIFIER { 2.5.4.6 }
PrintableString { "AU" }
}
}
SET {
SEQUENCE {
# stateOrProvinceName
OBJECT_IDENTIFIER { 2.5.4.8 }
UTF8String { "Some-State" }
}
}
SET {
SEQUENCE {
# organizationName
OBJECT_IDENTIFIER { 2.5.4.10 }
UTF8String { "Internet Widgits Pty Ltd" }
}
}
}
SEQUENCE {
SEQUENCE {
# rsaEncryption
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.1 }
NULL {}
}
BIT_STRING {
`00`
SEQUENCE {
INTEGER { `00d82bc8a632e462ff4df3d0ad598b45a7bdf147bf09587b22bd35ae97258694a080c0b41f7691674631d01084b7221e70239172c8e96d793a8577800fc4951675c54a714cc8633fa3f2639c2a4f9afacbc1716e288528a0271e651cae07d55b6f2d43ed2b90b18caf246daee9173a05c1bfb81cae653b1b58c2d9aed6aa6788f1` }
INTEGER { 65537 }
}
}
}
[3] {
SEQUENCE {
SEQUENCE {
# subjectKeyIdentifier
OBJECT_IDENTIFIER { 2.5.29.14 }
OCTET_STRING {
OCTET_STRING { `8b75d5accb08be0e1f65b7fa56be6ca775da85af` }
}
}
SEQUENCE {
# authorityKeyIdentifier
OBJECT_IDENTIFIER { 2.5.29.35 }
OCTET_STRING {
SEQUENCE {
[0 PRIMITIVE] { `8b75d5accb08be0e1f65b7fa56be6ca775da85af` }
}
}
}
SEQUENCE {
# basicConstraints
OBJECT_IDENTIFIER { 2.5.29.19 }
OCTET_STRING {
SEQUENCE {
BOOLEAN { TRUE }
}
}
}
}
}
})

SEQUENCE {
var("tbs_cert")
SEQUENCE {
# sha1WithRSAEncryption
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.5 }
NULL {}
}
BIT_STRING {
`00`
sign("RSA_PKCS1_SHA1", var("my_key"), var("tbs_cert"))
}
}
57 changes: 29 additions & 28 deletions samples/certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Modifying and creating X.509 certificates is more involved than modifying a
normal DER structure if one wishes to keep the signature valid. This document
provides instructions for fixing up a modified test certificate's signature if
the issuer's private key is available. (For a non-test certificate, this is the
provides instructions for using the `sign()` builtin to generate the signature
on-demand using the private key. (For a non-test certificate, this is the
CA's private key and is presumably unavailable.)

X.509 certificates are specified in [RFC 5280](https://tools.ietf.org/html/rfc5280).
Expand All @@ -17,31 +17,32 @@ The basic top-level structure is:
The `tbsCertificate` is a large structure with the contents of the certificate.
This includes the subject, issuer, public key, etc. The `signatureAlgorithm`
specifies the signature algorithm and parameters. Finally, the `signatureValue`
is the signature itself, created from the issuer's private key. This is the
field that must be fixed once the `tbsCertificate` is modified.

The signature is computed over the serialized `tbsCertificate`, so, using a
text editor, copy the `tbsCertificate` value into its own file, `tbs-cert.txt`.
Now sign that with the issuing private key. If using OpenSSL's command-line
tool, here is a sample command:

ascii2der -i tbs-cert.txt | openssl dgst -sha256 -sign issuer_key.pem | \
xxd -p -c 9999 > signature.txt

For other options, replace `-sha256` with a different digest or pass `-sigopt`.
See [OpenSSL's documentation](https://www.openssl.org/docs/man1.1.1/man1/dgst.html)
for details. Note that, for a valid certificate, the signature parameters
should match the `signatureAlgorithm` field. If using different signing
parameters, update it and the copy in the `tbsCertificate`.

Finally, in a text editor, replace the signature with the new one. X.509
defines certificates as BIT STRINGs, but every signature algorithm uses byte
strings, so include a leading zero to specify that no bits should be removed
from the end:

BIT_STRING {
`00` # No unused bits.
`INSERT SIGNATURE HERE`
is the signature itself, created from the issuer's private key. We can express
this relationship using a variable and `sign()`:

define("tbs_cert", SEQUENCE {
[0] { INTEGER { 2 } }
# Other X.509-ey goodness.
})

SEQUENCE {
# Splat in the actual tbsCertificate.
var("tbs_cert")
# This is the signatureAlgorithm.
SEQUENCE {
# ed25519
OBJECT_IDENTIFIER { 1.3.6.1.4.1.11591.15.1 }
}

# This is the signatureValue.
BIT_STRING {
`00` # No unused bits.
sign("ed25519", var("my_key"), var("tbs_cert"))
}
}

Finally, use `ascii2der` to convert the certificate to DER.
The variable `"my_key` would have been defined elsewhere in the file, or
potentially injected using the `-df` flag.

See `cert_with_sign.txt` for a complete example.

0 comments on commit 40301a7

Please sign in to comment.