Skip to content

Commit b13ddfb

Browse files
committed
Add sign() builtin for emitting digital signatures
1 parent ae6a969 commit b13ddfb

File tree

4 files changed

+276
-28
lines changed

4 files changed

+276
-28
lines changed

cmd/ascii2der/builtins.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@
1515
package main
1616

1717
import (
18+
"crypto"
19+
"crypto/ecdsa"
20+
"crypto/ed25519"
21+
"crypto/rsa"
22+
"crypto/x509"
1823
"errors"
1924
"fmt"
25+
"reflect"
2026
)
2127

2228
// NOTE: If adding a builtin, remember to document it in language.txt!
@@ -55,6 +61,96 @@ var builtins = map[string]func(*scanner, [][]byte) ([]byte, error){
5561
return nil, errors.New("expected one or two arguments to var()")
5662
}
5763
},
64+
65+
// sign(algorithm, key, message) expands into a digital signature for message
66+
// using the given algorithm and key. key must be a private key in PKCS #8
67+
// format.
68+
//
69+
// The supported algorithm strings are:
70+
// - "RSA_PKCS1_SHA1", RSA_PKCS1_SHA256", "RSA_PKCS1_SHA384",
71+
// "RSA_PKCS1_SHA512", for RSA-SSA with the specified hash function.
72+
// - "ECDSA_SHA256", "ECDSA_SHA384", "ECDSA_SHA512", for ECDSA with the
73+
// specified hash function.
74+
// - "Ed25519" for itself.
75+
"sign": func(scanner *scanner, args [][]byte) ([]byte, error) {
76+
if len(args) != 3 {
77+
return nil, errors.New("expected two arguments to sign()")
78+
}
79+
80+
pk8, err := x509.ParsePKCS8PrivateKey(args[1])
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
var signer crypto.Signer
86+
var hash crypto.Hash
87+
switch string(args[0]) {
88+
case "RSA_PKCS1_SHA1":
89+
key, ok := pk8.(*rsa.PrivateKey)
90+
if !ok {
91+
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
92+
}
93+
signer = key
94+
hash = crypto.SHA1
95+
case "RSA_PKCS1_SHA256":
96+
key, ok := pk8.(*rsa.PrivateKey)
97+
if !ok {
98+
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
99+
}
100+
signer = key
101+
hash = crypto.SHA256
102+
case "RSA_PKCS1_SHA384":
103+
key, ok := pk8.(*rsa.PrivateKey)
104+
if !ok {
105+
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
106+
}
107+
signer = key
108+
hash = crypto.SHA384
109+
case "RSA_PKCS1_SHA512":
110+
key, ok := pk8.(*rsa.PrivateKey)
111+
if !ok {
112+
return nil, fmt.Errorf("expected RSA key, got %v", reflect.TypeOf(key))
113+
}
114+
signer = key
115+
hash = crypto.SHA512
116+
case "ECSDA_SHA256":
117+
key, ok := pk8.(*ecdsa.PrivateKey)
118+
if !ok {
119+
return nil, fmt.Errorf("expected ECDSA key, got %v", reflect.TypeOf(key))
120+
}
121+
signer = key
122+
hash = crypto.SHA256
123+
case "ECSDA_SHA384":
124+
key, ok := pk8.(*ecdsa.PrivateKey)
125+
if !ok {
126+
return nil, fmt.Errorf("expected ECDSA key, got %v", reflect.TypeOf(key))
127+
}
128+
signer = key
129+
hash = crypto.SHA384
130+
case "ECSDA_SHA512":
131+
key, ok := pk8.(*ecdsa.PrivateKey)
132+
if !ok {
133+
return nil, fmt.Errorf("expected ECDSA key, got %v", reflect.TypeOf(key))
134+
}
135+
signer = key
136+
hash = crypto.SHA512
137+
case "Ed22519":
138+
key, ok := pk8.(ed25519.PrivateKey)
139+
if !ok {
140+
return nil, fmt.Errorf("expected Ed25519 key, got %v", reflect.TypeOf(key))
141+
}
142+
signer = key
143+
}
144+
145+
digest := args[2]
146+
if hash > 0 {
147+
hash := hash.New()
148+
hash.Write(digest)
149+
digest = hash.Sum(nil)
150+
}
151+
152+
return signer.Sign(nil, digest, hash)
153+
},
58154
}
59155

60156
func executeBuiltin(scanner *scanner, name string, args [][]byte) ([]byte, error) {

language.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ define(`ffff`, "payload")
305305
var("payload")
306306
var(var(`ffff`)) # Same as above, since var(`ffff`) expands to "payload".
307307

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

309313
# Disassembler.
310314

samples/cert_with_sign.txt

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# This is the same certificate as cert.txt, but with the signature generated
2+
# using sign().
3+
#
4+
# ascii2der will assemble both files to equal byte strings, because RSA-SSA
5+
# happens to be deterministic.
6+
7+
# Our private key, in PKCS #8 form.
8+
define("my_key", SEQUENCE {
9+
INTEGER { 0 }
10+
SEQUENCE {
11+
# rsaEncryption
12+
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.1 }
13+
NULL {}
14+
}
15+
OCTET_STRING {
16+
SEQUENCE {
17+
INTEGER { 0 }
18+
INTEGER { `00d82bc8a632e462ff4df3d0ad598b45a7bdf147bf09587b22bd35ae97258694a080c0b41f7691674631d01084b7221e70239172c8e96d793a8577800fc4951675c54a714cc8633fa3f2639c2a4f9afacbc1716e288528a0271e651cae07d55b6f2d43ed2b90b18caf246daee9173a05c1bfb81cae653b1b58c2d9aed6aa6788f1` }
19+
INTEGER { 65537 }
20+
INTEGER { `008072d3d15de033aafc88e9f0778ab8230a4c7a935b5c461ec84b43a8f0555daf599227f5a220983b2f92309e8bab2c66f9db8d5730cd2a01ca18cdf1909ffe2d791c40960c5161ea0b743f9cf43270a1c33666bdab55fc55c24f48fe5a618d07f8247a12a31972cb7234dbe9d45854948266156e38b2dc6d6215d74820809401` }
21+
INTEGER { `00f12f2c19ee1ecf2c999b87bdafde60eace3790faad8f9adec13b14c6dfb69f8795a1d0fe65494250b59534014b918453042012952ae6f5786342999600725491` }
22+
INTEGER { `00e57341d15469ec0bb5d389a0f0ada58a18d73776d9e69ef134049a918e475d4bea46f12d0b2468c972fc33a739a6bcdada8019376a0c466048d98278a2a49e61` }
23+
INTEGER { `0be99d8f0650e540b9b191e9cf96f74881b902e32ed169ffd8a1776c3f3e80f0ac765aa14615713e1549f250a20fe4ee48c4e0c6176162fc7842a0dd64d640d1` }
24+
INTEGER { `00e4d74e168bdd5499dd4fcc5d228ddda35ce111254d7010a7ba5cb91860d1d64007b99782783168fd39dc455c0c48bae47fb5f0f06ea92d6b8c5cbb1ebbfff921` }
25+
INTEGER { `00bef4572c74da6ba545cd36a288ef12685b07577950c973ad32b0690798dd9a86568231ef0765bd0a49fbb03aac3c1f94dadc97d23a03750132ba230408363ca1` }
26+
}
27+
}
28+
})
29+
30+
# The "to be signed" portion of our cert.
31+
define("tbs_cert", SEQUENCE {
32+
[0] {
33+
INTEGER { 2 }
34+
}
35+
INTEGER { `00fbb04c2eab109b0c` }
36+
SEQUENCE {
37+
# sha1WithRSAEncryption
38+
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.5 }
39+
NULL {}
40+
}
41+
SEQUENCE {
42+
SET {
43+
SEQUENCE {
44+
# countryName
45+
OBJECT_IDENTIFIER { 2.5.4.6 }
46+
PrintableString { "AU" }
47+
}
48+
}
49+
SET {
50+
SEQUENCE {
51+
# stateOrProvinceName
52+
OBJECT_IDENTIFIER { 2.5.4.8 }
53+
UTF8String { "Some-State" }
54+
}
55+
}
56+
SET {
57+
SEQUENCE {
58+
# organizationName
59+
OBJECT_IDENTIFIER { 2.5.4.10 }
60+
UTF8String { "Internet Widgits Pty Ltd" }
61+
}
62+
}
63+
}
64+
SEQUENCE {
65+
UTCTime { "140423205040Z" }
66+
UTCTime { "170422205040Z" }
67+
}
68+
SEQUENCE {
69+
SET {
70+
SEQUENCE {
71+
# countryName
72+
OBJECT_IDENTIFIER { 2.5.4.6 }
73+
PrintableString { "AU" }
74+
}
75+
}
76+
SET {
77+
SEQUENCE {
78+
# stateOrProvinceName
79+
OBJECT_IDENTIFIER { 2.5.4.8 }
80+
UTF8String { "Some-State" }
81+
}
82+
}
83+
SET {
84+
SEQUENCE {
85+
# organizationName
86+
OBJECT_IDENTIFIER { 2.5.4.10 }
87+
UTF8String { "Internet Widgits Pty Ltd" }
88+
}
89+
}
90+
}
91+
SEQUENCE {
92+
SEQUENCE {
93+
# rsaEncryption
94+
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.1 }
95+
NULL {}
96+
}
97+
BIT_STRING {
98+
`00`
99+
SEQUENCE {
100+
INTEGER { `00d82bc8a632e462ff4df3d0ad598b45a7bdf147bf09587b22bd35ae97258694a080c0b41f7691674631d01084b7221e70239172c8e96d793a8577800fc4951675c54a714cc8633fa3f2639c2a4f9afacbc1716e288528a0271e651cae07d55b6f2d43ed2b90b18caf246daee9173a05c1bfb81cae653b1b58c2d9aed6aa6788f1` }
101+
INTEGER { 65537 }
102+
}
103+
}
104+
}
105+
[3] {
106+
SEQUENCE {
107+
SEQUENCE {
108+
# subjectKeyIdentifier
109+
OBJECT_IDENTIFIER { 2.5.29.14 }
110+
OCTET_STRING {
111+
OCTET_STRING { `8b75d5accb08be0e1f65b7fa56be6ca775da85af` }
112+
}
113+
}
114+
SEQUENCE {
115+
# authorityKeyIdentifier
116+
OBJECT_IDENTIFIER { 2.5.29.35 }
117+
OCTET_STRING {
118+
SEQUENCE {
119+
[0 PRIMITIVE] { `8b75d5accb08be0e1f65b7fa56be6ca775da85af` }
120+
}
121+
}
122+
}
123+
SEQUENCE {
124+
# basicConstraints
125+
OBJECT_IDENTIFIER { 2.5.29.19 }
126+
OCTET_STRING {
127+
SEQUENCE {
128+
BOOLEAN { TRUE }
129+
}
130+
}
131+
}
132+
}
133+
}
134+
})
135+
136+
SEQUENCE {
137+
var("tbs_cert")
138+
SEQUENCE {
139+
# sha1WithRSAEncryption
140+
OBJECT_IDENTIFIER { 1.2.840.113549.1.1.5 }
141+
NULL {}
142+
}
143+
BIT_STRING {
144+
`00`
145+
sign("RSA_PKCS1_SHA1", var("my_key"), var("tbs_cert"))
146+
}
147+
}

samples/certificates.md

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

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

99
X.509 certificates are specified in [RFC 5280](https://tools.ietf.org/html/rfc5280).
@@ -17,31 +17,32 @@ The basic top-level structure is:
1717
The `tbsCertificate` is a large structure with the contents of the certificate.
1818
This includes the subject, issuer, public key, etc. The `signatureAlgorithm`
1919
specifies the signature algorithm and parameters. Finally, the `signatureValue`
20-
is the signature itself, created from the issuer's private key. This is the
21-
field that must be fixed once the `tbsCertificate` is modified.
22-
23-
The signature is computed over the serialized `tbsCertificate`, so, using a
24-
text editor, copy the `tbsCertificate` value into its own file, `tbs-cert.txt`.
25-
Now sign that with the issuing private key. If using OpenSSL's command-line
26-
tool, here is a sample command:
27-
28-
ascii2der -i tbs-cert.txt | openssl dgst -sha256 -sign issuer_key.pem | \
29-
xxd -p -c 9999 > signature.txt
30-
31-
For other options, replace `-sha256` with a different digest or pass `-sigopt`.
32-
See [OpenSSL's documentation](https://www.openssl.org/docs/man1.1.1/man1/dgst.html)
33-
for details. Note that, for a valid certificate, the signature parameters
34-
should match the `signatureAlgorithm` field. If using different signing
35-
parameters, update it and the copy in the `tbsCertificate`.
36-
37-
Finally, in a text editor, replace the signature with the new one. X.509
38-
defines certificates as BIT STRINGs, but every signature algorithm uses byte
39-
strings, so include a leading zero to specify that no bits should be removed
40-
from the end:
41-
42-
BIT_STRING {
43-
`00` # No unused bits.
44-
`INSERT SIGNATURE HERE`
20+
is the signature itself, created from the issuer's private key. We can express
21+
this relationship using a variable and `sign()`:
22+
23+
define("tbs_cert", SEQUENCE {
24+
[0] { INTEGER { 2 } }
25+
# Other X.509-ey goodness.
26+
})
27+
28+
SEQUENCE {
29+
# Splat in the actual tbsCertificate.
30+
var("tbs_cert")
31+
32+
# This is the signatureAlgorithm.
33+
SEQUENCE {
34+
# ed25519
35+
OBJECT_IDENTIFIER { 1.3.6.1.4.1.11591.15.1 }
36+
}
37+
38+
# This is the signatureValue.
39+
BIT_STRING {
40+
`00` # No unused bits.
41+
sign("ed25519", var("my_key"), var("tbs_cert"))
42+
}
4543
}
4644

47-
Finally, use `ascii2der` to convert the certificate to DER.
45+
The variable `"my_key` would have been defined elsewhere in the file, or
46+
potentially injected using the `-df` flag.
47+
48+
See `cert_with_sign.txt` for a complete example.

0 commit comments

Comments
 (0)