Created
January 1, 2015 07:27
-
-
Save philpennock/b73ccc58c2d36b414554 to your computer and use it in GitHub Desktop.
Query the NIST randomness beacon, in Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Author: Phil Pennock | |
// Based on publicly available data, as described below, so the only creative | |
// input was the expression in Golang. | |
package main | |
/* | |
An overview is at <http://www.nist.gov/itl/csd/ct/nist_beacon.cfm> and while | |
the REST API is reliably available (in my experience) the content at | |
<https://beacon.nist.gov/home> is not, so I'll replicate the data here from | |
when it was available: | |
This prototype implementation generates full-entropy bit-strings and posts | |
them in blocks of 512 bits every 60 seconds. Each such value is | |
sequence-numbered, time-stamped and signed, and includes the hash of the | |
previous value to chain the sequence of values together and prevent even the | |
source to retroactively change an output package without being detected. | |
Currently implemented calls are listed below. Users submitting a request need | |
to provide the record generation time in POSIX format (number of seconds | |
since midnight UTC, January 1, 1970 (see | |
http://en.wikipedia.org/wiki/Unix_time for more information and | |
http://www.epochconverter.com for an online timestamp converter.) | |
Current Record (or next closest): | |
https://beacon.nist.gov/rest/record/<timestamp> | |
Previous Record: | |
https://beacon.nist.gov/rest/record/previous/<timestamp> | |
Next Record: | |
https://beacon.nist.gov/rest/record/next/<timestamp> | |
Last Record: | |
https://beacon.nist.gov/rest/record/last | |
Start Chain Record: | |
https://beacon.nist.gov/rest/record/start-chain/<timestamp> | |
If a request for a next or previous record results in no record found, a 404 | |
response is returned. | |
Schema | |
The data source schema for the NIST Beacon REST API described above can be | |
viewed by clicking here (<https://beacon.nist.gov/record/0.1/beacon-0.1.0.xsd>). | |
Certificate | |
NIST Beacon is currently using an X.509 certificate with the Federal Common | |
Policy CA as the ultimate root authority. The certificate is available here | |
(<https://beacon.nist.gov/certificate/beacon.cer>). | |
*/ | |
// heavily influenced by http://hackaday.com/2014/12/19/nist-randomness-beacon/ | |
// both in selecting a source test data item and in understanding what data is | |
// present and how signature verification should be performed. In part because | |
// the beacon.nist.gov/home documentation page was unavailable for some time, | |
// when I first started writing this. | |
import ( | |
"bytes" | |
"crypto/x509" | |
"encoding/binary" | |
"encoding/hex" | |
"encoding/pem" | |
"encoding/xml" | |
"flag" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"net/http" | |
"os" | |
"strconv" | |
"time" | |
) | |
// Retrieved from https://beacon.nist.gov/certificate/beacon.cer on 2014-12-31 | |
// using link from http://hackaday.com/2014/12/19/nist-randomness-beacon/ | |
// Subject: C=US, O=U.S. Government, OU=Department of Commerce, OU=National Institute of Standards and Technology, OU=Devices, CN=beacon.nist.gov | |
// Issuer: C=US, O=Entrust, OU=Certification Authorities, OU=Entrust Managed Services SSP CA | |
// | |
const beaconCertPEM = ` | |
-----BEGIN CERTIFICATE----- | |
MIIHZTCCBk2gAwIBAgIESTWNPjANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJV | |
UzEQMA4GA1UEChMHRW50cnVzdDEiMCAGA1UECxMZQ2VydGlmaWNhdGlvbiBBdXRo | |
b3JpdGllczEoMCYGA1UECxMfRW50cnVzdCBNYW5hZ2VkIFNlcnZpY2VzIFNTUCBD | |
QTAeFw0xNDA1MDcxMzQ4MzZaFw0xNzA1MDcxNDE4MzZaMIGtMQswCQYDVQQGEwJV | |
UzEYMBYGA1UEChMPVS5TLiBHb3Zlcm5tZW50MR8wHQYDVQQLExZEZXBhcnRtZW50 | |
IG9mIENvbW1lcmNlMTcwNQYDVQQLEy5OYXRpb25hbCBJbnN0aXR1dGUgb2YgU3Rh | |
bmRhcmRzIGFuZCBUZWNobm9sb2d5MRAwDgYDVQQLEwdEZXZpY2VzMRgwFgYDVQQD | |
Ew9iZWFjb24ubmlzdC5nb3YwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB | |
AQC/m2xcckaSYztt6/6YezaUmqIqY5CLvrfO2esEIJyFg+cv7S7exL3hGYeDCnQL | |
VtUIGViAnO9yCXDC2Kymen+CekU7WEtSB96xz/xGrY3mbwjS46QSOND9xSRMroF9 | |
xbgqXxzJ7rL/0RMUkku3uurGb/cxUpzKt6ra7iUnzkk3BBk73kr2OXFyYYbtrN71 | |
s0B9qKKJZuPQqmA5n80Xc3E2YbaoAW4/gesncFNL7Sdxw9NIA1L4feu/o8xp3FNP | |
pv2e25C0113x+yagvb1W0mw6ISwAKhJ+6G4t4hFejl7RujuiDfORgzIhHMR4CyWt | |
PZFVn2qxZuVooj1+mduLIXhDAgMBAAGjggPKMIIDxjAOBgNVHQ8BAf8EBAMCBsAw | |
FwYDVR0gBBAwDjAMBgpghkgBZQMCAQMHMIIBXgYIKwYBBQUHAQEEggFQMIIBTDCB | |
uAYIKwYBBQUHMAKGgatsZGFwOi8vc3NwZGlyLm1hbmFnZWQuZW50cnVzdC5jb20v | |
b3U9RW50cnVzdCUyME1hbmFnZWQlMjBTZXJ2aWNlcyUyMFNTUCUyMENBLG91PUNl | |
cnRpZmljYXRpb24lMjBBdXRob3JpdGllcyxvPUVudHJ1c3QsYz1VUz9jQUNlcnRp | |
ZmljYXRlO2JpbmFyeSxjcm9zc0NlcnRpZmljYXRlUGFpcjtiaW5hcnkwSwYIKwYB | |
BQUHMAKGP2h0dHA6Ly9zc3B3ZWIubWFuYWdlZC5lbnRydXN0LmNvbS9BSUEvQ2Vy | |
dHNJc3N1ZWRUb0VNU1NTUENBLnA3YzBCBggrBgEFBQcwAYY2aHR0cDovL29jc3Au | |
bWFuYWdlZC5lbnRydXN0LmNvbS9PQ1NQL0VNU1NTUENBUmVzcG9uZGVyMBsGA1Ud | |
CQQUMBIwEAYJKoZIhvZ9B0QdMQMCASIwggGHBgNVHR8EggF+MIIBejCB6qCB56CB | |
5IaBq2xkYXA6Ly9zc3BkaXIubWFuYWdlZC5lbnRydXN0LmNvbS9jbj1XaW5Db21i | |
aW5lZDEsb3U9RW50cnVzdCUyME1hbmFnZWQlMjBTZXJ2aWNlcyUyMFNTUCUyMENB | |
LG91PUNlcnRpZmljYXRpb24lMjBBdXRob3JpdGllcyxvPUVudHJ1c3QsYz1VUz9j | |
ZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0O2JpbmFyeYY0aHR0cDovL3NzcHdlYi5t | |
YW5hZ2VkLmVudHJ1c3QuY29tL0NSTHMvRU1TU1NQQ0ExLmNybDCBiqCBh6CBhKSB | |
gTB/MQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRW50cnVzdDEiMCAGA1UECxMZQ2Vy | |
dGlmaWNhdGlvbiBBdXRob3JpdGllczEoMCYGA1UECxMfRW50cnVzdCBNYW5hZ2Vk | |
IFNlcnZpY2VzIFNTUCBDQTEQMA4GA1UEAxMHQ1JMNjY3MzArBgNVHRAEJDAigA8y | |
MDE0MDUwNzEzNDgzNlqBDzIwMTYwNjEyMTgxODM2WjAfBgNVHSMEGDAWgBTTzudb | |
iafNbJHGZzapWHIJ7OI58zAdBgNVHQ4EFgQUGIOcf6r7Z9wk+2/YuG5oTs7Qwk8w | |
CQYDVR0TBAIwADAZBgkqhkiG9n0HQQAEDDAKGwRWOC4xAwIEsDANBgkqhkiG9w0B | |
AQsFAAOCAQEASc+lZBbJWsHB2WnaBr8ZfBqpgS51Eh+wLchgIq7JHhVn+LagkR8C | |
XmvP57a0L/E+MRBqvH2RMqwthEcjXio2WIu/lyKZmg2go9driU6H3s89X8snblDF | |
1B+iL73vhkLVdHXgStMS8AHbm+3BW6yjHens1tVmKSowg1P/bGT3Z4nmamdY9oLm | |
9sCgFccthC1BQqtPv1XsmLshJ9vmBbYMsjKq4PmS0aLA59J01YMSq4U1kzcNS7wI | |
1/YfUrfeV+r+j7LKBgNQTZ80By2cfSalEqCe8oxqViAz6DsfPCBeE57diZNLiJmj | |
a2wWIBquIAXxvD8w2Bue7pZVeUHls5V5dA== | |
-----END CERTIFICATE----- | |
` | |
// https://beacon.nist.gov/rest/record/1400878200 | |
const debugRecord = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><record><version>Version 1.0</version><frequency>60</frequency><timeStamp>1400878200</timeStamp><seedValue>C4E0E995111B9986658709F759E57650859DB3DA5330F007CE9732BA2E30B1F8475E3882796DA835CBDBA6FD2D4F5345B4BB46A5F60AAC249C7D4E4670881E71</seedValue><previousOutputValue>D24EEBA206D05196C0ADC3DA2F56EA295CDD4D1C3E4D5265CCF762392CF8A4BCABA295DB441E6B9E661727FB1C37CE9DEC6D9465513356A13F515EC0CDB82D2C</previousOutputValue><signatureValue>AF8332249B8E7EBEDD0326740583FF2280FE788167F2E76C172A38858BFD3A346F5C87D879BC790BEE104CD96B5743B3E2C6B5E244C880C9362B85112C69B309277A1A97F970CC475864CF56A8A8430AFF61A8D89B2B9F537BC293E0944DAA054B77390A0D8E7844C25BDF8164D34D58C1E8DE503B62E55B311798072E276FE56F5C294FA76BE3A2BC47576BE5804A9AAC8307066C613C8507A459C898B25B502B975E5B17EAEF74F219C5C3C979E7188DDC473901EADA236C6127ABF72A2C258E95F90A4BF6F67EFAD8D66DC19C169B543B3F0F12A2D520A7E6489CE2509930592D50CD663961C10F1F2584BD89F79FBA0C0980F00D062F1ECC51A339E6B11F</signatureValue><outputValue>68ACCF41E370AA4AC3F83CCF7DA56AE8E87AA6EE491E68C2A92C661D1267697AA21FDFAE17D0701A49AA9EAA74016B4A4AD1DDD45D62961141E9C7C8FBD1FA6A</outputValue><statusCode>0</statusCode></record>` | |
const debugTimestamp = 1400878200 | |
// beaconCertificate gives us the beacon certificate as a Golang object; it | |
// always succeeds, because the beacon is a const encoded herein, so a failure | |
// is panic-worthy. | |
func beaconCertificate() *x509.Certificate { | |
pemBlock, remainder := pem.Decode([]byte(beaconCertPEM)) | |
if len(remainder) > 0 { | |
panic(fmt.Sprintf("have %d bytes left in PEM", len(remainder))) | |
} | |
certChain, err := x509.ParseCertificates(pemBlock.Bytes) | |
if err != nil { | |
panic(err.Error()) | |
} | |
if len(certChain) != 1 { | |
panic(fmt.Sprintf("have %d certificates in beacon PEM, not 1", len(certChain))) | |
} | |
return certChain[0] | |
} | |
func fetchRecord(timestamp int64) ([]byte, error) { | |
if timestamp == 0 { | |
timestamp = debugTimestamp | |
return []byte(debugRecord), nil | |
} | |
resp, err := http.Get(fmt.Sprintf("https://beacon.nist.gov/rest/record/%d", timestamp)) | |
if err != nil { | |
return nil, err | |
} | |
defer resp.Body.Close() | |
body, err := ioutil.ReadAll(resp.Body) | |
return body, err | |
} | |
type BeaconData struct { | |
XMLName xml.Name `xml:"record"` | |
Version string `xml:"version"` | |
Frequency uint32 `xml:"frequency"` | |
TimeStamp uint64 `xml:"timeStamp"` | |
SeedValue string `xml:"seedValue"` | |
PreviousOutputValue string `xml:"previousOutputValue"` | |
SignatureValue string `xml:"signatureValue"` | |
OutputValue string `xml:"outputValue"` | |
StatusCode uint32 `xml:"statusCode"` | |
} | |
func (d BeaconData) VerificationData() (signed, signature []byte, err error) { | |
signature, err = hex.DecodeString(d.SignatureValue) | |
if err != nil { | |
return nil, nil, err | |
} | |
/* | |
http://hackaday.com/2014/12/19/nist-randomness-beacon/ | |
## Create a bytewise reversed version of the listed signature | |
## This is necessary b/c Beacon signs with Microsoft CryptoAPI which outputs | |
## the signature as little-endian instead of big-endian like many other tools | |
## This may change (personal communication) in a future revision of the Beacon | |
*/ | |
sigLimit := len(signature) - 1 | |
for i := 0; i <= sigLimit/2; i++ { | |
signature[i], signature[sigLimit-i] = signature[sigLimit-i], signature[i] | |
} | |
b := new(bytes.Buffer) | |
b.Grow(200) | |
_, _ = b.WriteString(d.Version) | |
binary.Write(b, binary.BigEndian, d.Frequency) | |
binary.Write(b, binary.BigEndian, d.TimeStamp) | |
seed, err := hex.DecodeString(d.SeedValue) | |
if err != nil { | |
return nil, nil, err | |
} | |
_, _ = b.Write(seed) | |
prev, err := hex.DecodeString(d.PreviousOutputValue) | |
if err != nil { | |
return nil, nil, err | |
} | |
_, _ = b.Write(prev) | |
binary.Write(b, binary.BigEndian, d.StatusCode) | |
return b.Bytes(), signature, nil | |
} | |
type beaconMaker struct { | |
Debug bool | |
DebugFH io.Writer | |
verifyCert *x509.Certificate | |
} | |
func NewBeaconMaker() beaconMaker { | |
return beaconMaker{ | |
verifyCert: beaconCertificate(), | |
} | |
} | |
func (m beaconMaker) Debugf(format string, a ...interface{}) { | |
if !m.Debug { | |
return | |
} | |
if m.DebugFH == nil { | |
// might later capture in a buffer within the maker, to print, but then | |
// maker is mutable; for now, just require both the flag and the writer | |
return | |
} | |
fmt.Fprintf(m.DebugFH, format, a...) | |
} | |
func (m beaconMaker) DebugCertificate() { | |
m.Debugf("certificate: %#v\n", m.verifyCert) | |
} | |
func (m beaconMaker) ValidateSignature(signed, signature []byte) error { | |
return m.verifyCert.CheckSignature(x509.SHA512WithRSA, signed, signature) | |
} | |
func (m beaconMaker) NewByXMLBytes(rawxml []byte) (BeaconData, error) { | |
var bd BeaconData | |
err := xml.Unmarshal(rawxml, &bd) | |
if err != nil { | |
return BeaconData{}, fmt.Errorf("decoding beacon data from XML failed: %s", err) | |
} | |
m.Debugf("raw beacon XML: %#v\n", bd) | |
verifiable, signature, err := bd.VerificationData() | |
if err != nil { | |
return bd, fmt.Errorf("preparing beacon data for signature verification failed: %s", err) | |
} | |
if len(verifiable) == 0 { | |
panic("beacon signature verifiable form of length 0") | |
} | |
if len(signature) == 0 { | |
panic("beacon signature of length 0") | |
} | |
if m.Debug { | |
m.Debugf("verification form: %s\nsignature: %s\n", hex.EncodeToString(verifiable), hex.EncodeToString(signature)) | |
} | |
err = m.ValidateSignature(verifiable, signature) | |
if err != nil { | |
return bd, fmt.Errorf("failed to verify beacon signature: %s", err) | |
} | |
m.Debugf("signature verification succeeded\n") | |
return bd, nil | |
} | |
func (m beaconMaker) NewByTimestamp(ts int64) (BeaconData, error) { | |
if ts%60 != 0 { | |
m.Debugf("trimming %d seconds from %d\n", ts%60, ts) | |
ts -= ts % 60 | |
} | |
m.Debugf("fetching beacon for timestamp: %d\n", ts) | |
rawBeaconData, err := fetchRecord(ts) | |
if err != nil { | |
return BeaconData{}, fmt.Errorf("fetching beacon data failed: %s", err) | |
} | |
m.Debugf("fetched %d octets in beacon data\n", len(rawBeaconData)) | |
m.Debugf("RAW: %q\n\n", rawBeaconData) | |
return m.NewByXMLBytes(rawBeaconData) | |
} | |
var beaconCmdlineFlags struct { | |
debug bool | |
} | |
func init() { | |
flag.BoolVar(&beaconCmdlineFlags.debug, "beacon-debug", false, "debug beacon operation") | |
} | |
func main() { | |
flag.Parse() | |
maker := NewBeaconMaker() | |
if beaconCmdlineFlags.debug { | |
maker.Debug = true | |
maker.DebugFH = os.Stderr | |
//maker.DebugCertificate() | |
} | |
var err error | |
timeStamp := time.Now().Unix() | |
if len(flag.Args()) > 0 { | |
timeStamp, err = strconv.ParseInt(flag.Arg(0), 0, 64) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Usage: %s [<timestamp-Unix-epoch-secs>]\n", os.Args[0]) | |
fmt.Fprintln(os.Stderr, " A timestamp of 0 uses a hard-coded XML fragment in the source code") | |
os.Exit(1) | |
} | |
} | |
beacon, err := maker.NewByTimestamp(timeStamp) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "beacon object creation failed: %s\n", err) | |
os.Exit(1) | |
} | |
seed, err := hex.DecodeString(beacon.SeedValue) | |
if err != nil { | |
// should have been caught during verification | |
panic(err.Error()) | |
} | |
fmt.Printf("%v\n", seed) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment