Merge remote-tracking branch 'origin/feature/revocation' into feature/pdf-signer
This commit is contained in:
55
revocation/revocation.go
Normal file
55
revocation/revocation.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package revocation
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
// InfoArchival is the pkcs7 container containing the revocation information for
|
||||
// all embedded certificates.
|
||||
//
|
||||
// Currently the internal structure is exposed but I don't like to expose the
|
||||
// asn1.RawValue objects. We can probably make them private and expose the
|
||||
// information with functions.
|
||||
type InfoArchival struct {
|
||||
CRL CRL `asn1:"tag:0,optional,explicit"`
|
||||
OCSP OCSP `asn1:"tag:1,optional,explicit"`
|
||||
Other Other `asn1:"tag:2,optional,explicit"`
|
||||
}
|
||||
|
||||
// AddCRL is used to embed an CRL to revocation.InfoArchival object. You directly
|
||||
// pass the bytes of a downloaded CRL to this function.
|
||||
func (r *InfoArchival) AddCRL(b []byte) error {
|
||||
r.CRL = append(r.CRL, asn1.RawValue{FullBytes: b})
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddOCSP is used to embed the raw bytes of an OCSP response.
|
||||
func (r *InfoArchival) AddOCSP(b []byte) error {
|
||||
r.OCSP = append(r.OCSP, asn1.RawValue{FullBytes: b})
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRevoked checks if there is a status inclded for the certificate and returns
|
||||
// true if the certificate is marked as revoked.
|
||||
//
|
||||
// TODO: We should report if there is no CRL or OCSP response embeded for this certificate
|
||||
// TODO: Information about the revocation (time, reason, etc) must be extractable
|
||||
func (r *InfoArchival) IsRevoked(c *x509.Certificate) bool {
|
||||
// check the crl and ocsp to see if this certificate is revoked
|
||||
return true
|
||||
}
|
||||
|
||||
// CRL contains the raw bytes of a pkix.CertificateList and can be parsed with
|
||||
// x509.PParseCRL.
|
||||
type CRL []asn1.RawValue
|
||||
|
||||
// OCSP contains the raw bytes of an OCSP response and can be parsed with
|
||||
// x/crypto/ocsp.ParseResponse
|
||||
type OCSP []asn1.RawValue
|
||||
|
||||
// ANS.1 Object OtherRevInfo
|
||||
type Other struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value []byte
|
||||
}
|
88
sign/revocation.go
Normal file
88
sign/revocation.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
func embedOCSPRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||
req, err := ocsp.CreateRequest(cert, issuer, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ocspUrl := fmt.Sprintf("%s/%s", strings.TrimRight(cert.OCSPServer[0], "/"),
|
||||
base64.StdEncoding.EncodeToString(req))
|
||||
resp, err := http.Get(ocspUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if we got a valid OCSP response
|
||||
_, err = ocsp.ParseResponseForCert(body, cert, issuer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.AddOCSP(body)
|
||||
return nil
|
||||
}
|
||||
|
||||
// embedCRLRevocationStatus requires an issuer as it needs to implement the
|
||||
// the interface, a nil argment might be given if the issuer is not known.
|
||||
func embedCRLRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||
resp, err := http.Get(cert.CRLDistributionPoints[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: verify crl and certificate before embedding
|
||||
i.AddCRL(body)
|
||||
return nil
|
||||
}
|
||||
|
||||
func embedRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||
// For each certificate a revoction status needs to be included, this can be done
|
||||
// by embedding a CRL or OCSP response. In most cases an OCSP response is smaller
|
||||
// to embed in the document but and empty CRL (often seen of dediced high volume
|
||||
// hirachies) can be smaller.
|
||||
//
|
||||
// There have been some reports that the usage of a CRL would result in a better
|
||||
// compatibilty.
|
||||
//
|
||||
// TODO: Find and embed link about compatibilty
|
||||
// TODO: Implement revocation status caching (required for higher volume signing)
|
||||
|
||||
// using an OCSP server
|
||||
if len(cert.OCSPServer) > 0 {
|
||||
embedOCSPRevocationStatus(cert, issuer, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// using a crl
|
||||
if len(cert.CRLDistributionPoints) > 0 {
|
||||
embedCRLRevocationStatus(cert, issuer, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("certificate contains no information to check status")
|
||||
}
|
119
sign/revocation_test.go
Normal file
119
sign/revocation_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||
)
|
||||
|
||||
const certPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIGKDCCBRCgAwIBAgIMW8J4m7huCPO5f+wbMA0GCSqGSIb3DQEBCwUAMEsxCzAJ
|
||||
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhH
|
||||
bG9iYWxTaWduIENBIDIgZm9yIEFBVEwwHhcNMTcwNzA2MDk0MDUyWhcNMjAwNzA2
|
||||
MDk0MDUyWjCBpzELMAkGA1UEBhMCR0IxDTALBgNVBAgTBEtlbnQxEjAQBgNVBAcT
|
||||
CU1haWRzdG9uZTEfMB0GA1UEChMWR01PIEdsb2JhbFNpZ24gTGltaXRlZDEfMB0G
|
||||
A1UEAxMWUGF1bCBWYW4gQnJvdXdlcnNoYXZlbjEzMDEGCSqGSIb3DQEJARYkcGF1
|
||||
bC52YW5icm91d2Vyc2hhdmVuQGdsb2JhbHNpZ24uY29tMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEAr5jbAIZDjkWngxlwJqneE9VEDTvmMIGwvgy71g5j
|
||||
k+igHxB6tTfaqGD87oIm2wcrlZHpPJG9n2Rh9FhFmvx8ZXiceNI9Ks5Ho5iYNFUk
|
||||
y2JuVfFPxtp6amqpLzM5HZUePgu1Gdy1Zn1PUajii7paFPuhdemcA9DdTAQ1GsDv
|
||||
C9MZ2D5sKM0hLCRePCzJ3TeQmHefFrC0XQ7u2i7LDD990URiFz7WNq2tSDJwBe/6
|
||||
1tMewpekmtE5X43PqzgcyGBsDJqAKcthsLQnhqrIryuwE2bEhP/FxQrHkT+f/OkK
|
||||
vwAjB9gYgJRNoMFUwXLW789JsOkbCqepoWsg1PnWrAbAMQIDAQABo4ICrTCCAqkw
|
||||
DgYDVR0PAQH/BAQDAgeAMIGIBggrBgEFBQcBAQR8MHowQQYIKwYBBQUHMAKGNWh0
|
||||
dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYWF0bDJzaGEyZzIu
|
||||
Y3J0MDUGCCsGAQUFBzABhilodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vZ3Nh
|
||||
YXRsMnNoYTJnMjCB2wYDVR0gBIHTMIHQMIHNBgsrBgEEAaAyASgeAjCBvTCBhgYI
|
||||
KwYBBQUHAgIwegx4VGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQgaW4g
|
||||
YWNjb3JkYW5jZSB3aXRoIHRoZSBHbG9iYWxTaWduIENQUyBsb2NhdGVkIGF0IGh0
|
||||
dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDIGCCsGAQUFBwIB
|
||||
FiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAJBgNVHRME
|
||||
AjAAMD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20v
|
||||
Z3MvZ3NhYXRsMnNoYTJnMi5jcmwwLwYDVR0RBCgwJoEkcGF1bC52YW5icm91d2Vy
|
||||
c2hhdmVuQGdsb2JhbHNpZ24uY29tMFwGCiqGSIb3LwEBCQEETjBMAgEBhkRodHRw
|
||||
Oi8vYWF0bC10aW1lc3RhbXAuZ2xvYmFsc2lnbi5jb20vdHNhL2FvaGZld2F0MjM4
|
||||
OTUzNWZuYXNnbmxnNW0yMwEBADATBgNVHSUEDDAKBggrBgEFBQcDBDAdBgNVHQ4E
|
||||
FgQUtimjVds00OgBZ747tqkKbZJeCCowHwYDVR0jBBgwFoAUxRNOzofGiRsj6EDj
|
||||
dTKbA3A67+8wDQYJKoZIhvcNAQELBQADggEBACBUFVwUKVpOWt1eLf7lKKPfVhEL
|
||||
9QrkAkV/UZPMsDwBDIJhphqjCqfbJVTgybm79gUCJiwbarCYOHRgFAdNTPEvEcT0
|
||||
+XwR6WZcDdfQAtaHfO6X9ExgJv93txoFVcpYLY1hR3o6QdP4VQSDhRTv3bM1j/WC
|
||||
mcCoiIQz28Y8L+8rRx5J7JAgYpupoU/8sCpidBMhYAGF5p8Z8p0LbqvZndRHaVqp
|
||||
yXQ0kYj1n45it5FXsKECWZKTx0v4IBySJY3RGpF+5cpPUYulJfINBg7nj7aQG/Uv
|
||||
qtyxnVAG3W4pTHWd/0Gyc3lrgRtyZy+b9DaxHZ/N6HHNgnRHB4PUkektpX4=
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const issuerPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIEpjCCA46gAwIBAgIORea3r9ymcb22XRTz2sAwDQYJKoZIhvcNAQELBQAwVzEL
|
||||
MAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExLTArBgNVBAMT
|
||||
JEdsb2JhbFNpZ24gQ0EgZm9yIEFBVEwgLSBTSEEyNTYgLSBHMjAeFw0xNDEyMTAw
|
||||
MDAwMDBaFw0yNDEyMTAwMDAwMDBaMEsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBH
|
||||
bG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhHbG9iYWxTaWduIENBIDIgZm9yIEFB
|
||||
VEwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJS4vhNfaSmXtX7bWy
|
||||
hVhBGqisbqgnX9/K5psHMrkwm10pnicCSmvb6nvgMAPbRPyfpGHj5ArrDli6rCDR
|
||||
CLR1tjied/6AxQCCPgyvDyEwDWxzytVHkCldzEHjHmc1kL0zI7aQfNrD25xAUnHa
|
||||
X2jBm71filgduyQBfuLJLlL/NGh46X6eI9xpqBmqwBFa6wHPisnwkAB22CQB/OY3
|
||||
mnlhLCJmrEL9fGPRDQnc6w849ws3nkKISkEPmTyfUAJUkEgCxOjfiwoNSFjZ99lA
|
||||
K+ujj90dqYU3mJ7dTEHA2+dFrdvVsoyA4JZBu7utxe2rJgpifO3B/L+f4Cat12kA
|
||||
2IqtAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
|
||||
/wIBADAdBgNVHQ4EFgQUxRNOzofGiRsj6EDjdTKbA3A67+8wHwYDVR0jBBgwFoAU
|
||||
YMLxUj6tjBPc28oO+grmKiyZS9gwgYYGCCsGAQUFBwEBBHoweDA0BggrBgEFBQcw
|
||||
AYYoaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzYWF0bHNoYTJnMjBABggr
|
||||
BgEFBQcwAoY0aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nh
|
||||
YXRsc2hhMmcyLmNydDA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vY3JsLmdsb2Jh
|
||||
bHNpZ24uY29tL2dzL2dzYWF0bHNoYTJnMi5jcmwwRwYDVR0gBEAwPjA8BgRVHSAA
|
||||
MDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z
|
||||
aXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAkH9vI0bJPzbtf5ikdFb5Kc4L9/IIZ
|
||||
GPeVbS3UPl8x4WPzmZHwGvSeG53DDcJ9weGHO4PORBgBT5KDZEt7CKXd4vt83xw8
|
||||
P1kYvvjv4E+8R8VD3hP48zkygHMR3JtBdMPkbNbE10TCb4XmQPpkwGIVhaD7ojtS
|
||||
+4mPjVts6ZZzbnHI42CbYwdaOf2W8IUu0b1w4T7T5YPfi8rSKwQxIKibFG1mSsOC
|
||||
vG9tDxJVJUNdiWTPoHGn+n+3qeJvPRgHhicZ+ivOqqmLQSNgtp1WdQ+uJmUqU7EY
|
||||
htN+laG7bS/8xGTPothL9Abgd/9L3X0KKGUDCdcpzRuy20CI7E4uygD8
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
func TestEmbedRevocationStatus(t *testing.T) {
|
||||
var ia revocation.InfoArchival
|
||||
|
||||
err := embedRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
||||
if len(ia.OCSP) != 1 {
|
||||
t.Errorf("Expected one OCSP status")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedOCSPRevocationStatus(t *testing.T) {
|
||||
var ia revocation.InfoArchival
|
||||
|
||||
err := embedOCSPRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
||||
if len(ia.OCSP) != 1 {
|
||||
t.Errorf("Expected one OCSP status")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedCRLRevocationStatus(t *testing.T) {
|
||||
var ia revocation.InfoArchival
|
||||
|
||||
err := embedCRLRevocationStatus(pemToCert(certPem), nil, &ia)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
||||
if len(ia.CRL) != 1 {
|
||||
t.Errorf("Expected one CRL")
|
||||
}
|
||||
}
|
||||
|
||||
func pemToCert(p string) *x509.Certificate {
|
||||
block, _ := pem.Decode([]byte(p))
|
||||
cert, _ := x509.ParseCertificate(block.Bytes)
|
||||
|
||||
return cert
|
||||
}
|
@@ -11,26 +11,13 @@ import (
|
||||
"time"
|
||||
|
||||
"bitbucket.org/digitorus/pdf"
|
||||
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||
"github.com/digitorus/pkcs7"
|
||||
"github.com/digitorus/timestamp"
|
||||
"go/src/log"
|
||||
"log"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
type RevocationInfoArchival struct {
|
||||
CRL RevCRL `asn1:"tag:0,optional,explicit"`
|
||||
OCSP RevOCSP `asn1:"tag:1,optional,explicit"`
|
||||
OtherRevInfo OtherRevInfo `asn1:"tag:2,optional,explicit"`
|
||||
}
|
||||
|
||||
type RevCRL []asn1.RawValue
|
||||
type RevOCSP []asn1.RawValue
|
||||
|
||||
type OtherRevInfo struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Error string
|
||||
|
||||
@@ -192,7 +179,7 @@ func Verify(file *os.File) (apiResp *Response, err error) {
|
||||
}
|
||||
|
||||
// PDF signature certificate revocation information attribute (1.2.840.113583.1.1.8)
|
||||
var revInfo RevocationInfoArchival
|
||||
var revInfo revocation.InfoArchival
|
||||
p7.UnmarshalSignedAttribute(asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, &revInfo)
|
||||
|
||||
// Parse OCSP response
|
||||
|
Reference in New Issue
Block a user